diff --git a/bench/tests/power-macro/PowerMacro.scala b/bench/tests/power-macro/PowerMacro.scala index 31d8f77987d6..cc8ac09b7d2a 100644 --- a/bench/tests/power-macro/PowerMacro.scala +++ b/bench/tests/power-macro/PowerMacro.scala @@ -2,7 +2,7 @@ import scala.quoted.Expr object PowerMacro { - inline def power(inline n: Long, x: Double) = ~powerCode(n, '(x)) + transparent def power(transparent n: Long, x: Double) = ~powerCode(n, '(x)) def powerCode(n: Long, x: Expr[Double]): Expr[Double] = if (n == 0) '(1.0) diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index 45ac2570e643..75efba2b1ef6 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -433,7 +433,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma val Flag_METHOD: Flags = Flags.Method.bits val ExcludedForwarderFlags: Flags = { Flags.Specialized | Flags.Lifted | Flags.Protected | Flags.JavaStatic | - Flags.Bridge | Flags.VBridge | Flags.Private | Flags.Macro + Flags.Bridge | Flags.Private | Flags.Macro }.bits def isQualifierSafeToElide(qual: Tree): Boolean = tpd.isIdempotentExpr(qual) diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index 3fc50449978a..f66313546081 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -5,7 +5,7 @@ import core.Types.Type // Do not remove me #3383 import util.SourceFile import ast.{tpd, untpd} import tpd.{ Tree, TreeTraverser } -import typer.Inliner.InlineAccessors +import typer.PrepareTransparent.InlineAccessors import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.SymDenotations.ClassDenotation import dotty.tools.dotc.core.Symbols._ diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 99d2ee745535..7e5ef0d7d2a3 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -80,8 +80,7 @@ class Compiler { new StringInterpolatorOpt, // Optimizes raw and s string interpolators by rewriting them to string concatentations new CrossCastAnd, // Normalize selections involving intersection types. new Splitter) :: // Expand selections involving union types into conditionals - List(new ErasedDecls, // Removes all erased defs and vals decls (except for parameters) - new IsInstanceOfChecker, // check runtime realisability for `isInstanceOf` + List(new PruneErasedDefs, // Drop erased definitions from scopes and simplify erased expressions new VCInlineMethods, // Inlines calls to value class methods new SeqLiterals, // Express vararg arguments as arrays new InterceptedMethods, // Special handling of `==`, `|=`, `getClass` methods @@ -119,7 +118,6 @@ class Compiler { new SelectStatic, // get rid of selects that would be compiled into GetStatic new CollectEntryPoints, // Find classes with main methods new CollectSuperCalls, // Find classes that are called with super - new DropInlined, // Drop Inlined nodes, since backend has no use for them new LabelDefs) :: // Converts calls to labels to jumps Nil diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 2516d4bf406e..7be5b53515a5 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -391,12 +391,6 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => SimplyPure case TypeApply(fn, _) => exprPurity(fn) -/* - * Not sure we'll need that. Comment out until we find out - case Apply(Select(free @ Ident(_), nme.apply), _) if free.symbol.name endsWith nme.REIFY_FREE_VALUE_SUFFIX => - // see a detailed explanation of this trick in `GenSymbols.reifyFreeTerm` - free.symbol.hasStableFlag && isIdempotentExpr(free) -*/ case Apply(fn, args) => def isKnownPureOp(sym: Symbol) = sym.owner.isPrimitiveValueClass || sym.owner == defn.StringClass @@ -424,6 +418,8 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => def isPureExpr(tree: Tree)(implicit ctx: Context) = exprPurity(tree) >= Pure def isIdempotentExpr(tree: Tree)(implicit ctx: Context) = exprPurity(tree) >= Idempotent + def isPureBinding(tree: Tree)(implicit ctx: Context) = statPurity(tree) >= Pure + /** The purity level of this reference. * @return * SimplyPure if reference is (nonlazy and stable) or to a parameterized function @@ -460,11 +456,11 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => * Strictly speaking we can't replace `O.x` with `42`. But this would make * most expressions non-constant. Maybe we can change the spec to accept this * kind of eliding behavior. Or else enforce true purity in the compiler. - * The choice will be affected by what we will do with `inline` and with + * The choice will be affected by what we will do with `transparent` and with * Singleton type bounds (see SIP 23). Presumably * * object O1 { val x: Singleton = 42; println("43") } - * object O2 { inline val x = 42; println("43") } + * object O2 { transparent val x = 42; println("43") } * * should behave differently. * @@ -474,8 +470,8 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => * * O2.x = 42 * - * Revisit this issue once we have implemented `inline`. Then we can demand - * purity of the prefix unless the selection goes to an inline val. + * Revisit this issue once we have standardized on `transparent`. Then we can demand + * purity of the prefix unless the selection goes to a transparent val. * * Note: This method should be applied to all term tree nodes that are not literals, * that can be idempotent, and that can have constant types. So far, only nodes @@ -585,11 +581,15 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => /** An extractor for def of a closure contained the block of the closure. */ object closureDef { - def unapply(tree: Tree): Option[DefDef] = tree match { - case Block(Nil, expr) => unapply(expr) + def unapply(tree: Tree)(implicit ctx: Context): Option[DefDef] = tree match { case Block((meth @ DefDef(nme.ANON_FUN, _, _, _, _)) :: Nil, closure: Closure) => Some(meth) - case _ => None + case Block(Nil, expr) => + unapply(expr) + case Inlined(_, bindings, expr) if bindings.forall(isPureBinding) => + unapply(expr) + case _ => + None } } @@ -710,6 +710,15 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => Nil } + /** The qualifier part of a Select or Ident. + * For an Ident, this is the `This` of the current class. + */ + def qualifier(tree: Tree)(implicit ctx: Context) = tree match { + case Select(qual, _) => qual + case tree: Ident => desugarIdentPrefix(tree) + case _ => This(ctx.owner.enclosingClass.asClass) + } + /** Is this a selection of a member of a structural type that is not a member * of an underlying class or trait? */ diff --git a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala index e52213cf0485..d854482537ec 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -11,7 +11,7 @@ import core.tasty.TreePickler.Hole /** A map that applies three functions and a substitution together to a tree and * makes sure they are coordinated so that the result is well-typed. The functions are - * @param typeMap A function from Type to Type that gets applied to the + * @param typeMap A function from Type to Type that gets applied to the * type of every tree node and to all locally defined symbols, * followed by the substitution [substFrom := substTo]. * @param treeMap A transformer that translates all encountered subtrees in @@ -95,7 +95,7 @@ class TreeTypeMap( val (tmap2, vparamss1) = tmap1.transformVParamss(vparamss) val res = cpy.DefDef(ddef)(name, tparams1, vparamss1, tmap2.transform(tpt), tmap2.transform(ddef.rhs)) res.symbol.transformAnnotations { - case ann: BodyAnnotation => ann.derivedAnnotation(res.rhs) + case ann: BodyAnnotation => ann.derivedAnnotation(transform(ann.tree)) case ann => ann } res @@ -126,7 +126,7 @@ class TreeTypeMap( override def transformStats(trees: List[tpd.Tree])(implicit ctx: Context) = transformDefs(trees)._2 - private def transformDefs[TT <: tpd.Tree](trees: List[TT])(implicit ctx: Context): (TreeTypeMap, List[TT]) = { + def transformDefs[TT <: tpd.Tree](trees: List[TT])(implicit ctx: Context): (TreeTypeMap, List[TT]) = { val tmap = withMappedSyms(tpd.localSyms(trees)) (tmap, tmap.transformSub(trees)) } diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 9f72feb216b9..1213661673b2 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -589,6 +589,7 @@ object Trees { case class Inlined[-T >: Untyped] private[ast] (call: tpd.Tree, bindings: List[MemberDef[T]], expansion: Tree[T]) extends Tree[T] { type ThisTree[-T >: Untyped] = Inlined[T] + override def initialPos = call.pos } /** A type tree that represents an existing or inferred type */ @@ -922,6 +923,11 @@ object Trees { case ys => Thicket(ys) } + /** Extractor for the synthetic scrutinee tree of an implicit match */ + object ImplicitScrutinee { + def apply() = Ident(nme.IMPLICITkw) + def unapply(id: Ident): Boolean = id.name == nme.IMPLICITkw && !id.isInstanceOf[BackquotedIdent] + } // ----- Helper classes for copying, transforming, accumulating ----------------- val cpy: TreeCopier @@ -1109,6 +1115,10 @@ object Trees { case tree: Annotated if (arg eq tree.arg) && (annot eq tree.annot) => tree case _ => finalize(tree, untpd.Annotated(arg, annot)) } + def UntypedSplice(tree: Tree)(splice: untpd.Tree) = tree match { + case tree: tpd.UntypedSplice if tree.splice `eq` splice => tree + case _ => finalize(tree, tpd.UntypedSplice(splice)) + } def Thicket(tree: Tree)(trees: List[Tree]): Thicket = tree match { case tree: Thicket if trees eq tree.trees => tree case _ => finalize(tree, untpd.Thicket(trees)) @@ -1146,7 +1156,7 @@ object Trees { */ protected def inlineContext(call: Tree)(implicit ctx: Context): Context = ctx - abstract class TreeMap(val cpy: TreeCopier = inst.cpy) { + abstract class TreeMap(val cpy: TreeCopier = inst.cpy) { self => def transform(tree: Tree)(implicit ctx: Context): Tree = { Stats.record(s"TreeMap.transform $getClass") @@ -1245,8 +1255,8 @@ object Trees { case Thicket(trees) => val trees1 = transform(trees) if (trees1 eq trees) tree else Thicket(trees1) - case _ if ctx.reporter.errorsReported => - tree + case _ => + transformMoreCases(tree) } } @@ -1258,9 +1268,26 @@ object Trees { transform(tree).asInstanceOf[Tr] def transformSub[Tr <: Tree](trees: List[Tr])(implicit ctx: Context): List[Tr] = transform(trees).asInstanceOf[List[Tr]] + + protected def transformMoreCases(tree: Tree)(implicit ctx: Context): Tree = tree match { + case tpd.UntypedSplice(usplice) => + // For a typed tree map: homomorphism on the untyped part with + // recursive mapping of typed splices. + // The case is overridden in UntypedTreeMap.## + val untpdMap = new untpd.UntypedTreeMap { + override def transform(tree: untpd.Tree)(implicit ctx: Context): untpd.Tree = tree match { + case untpd.TypedSplice(tsplice) => + untpd.cpy.TypedSplice(tree)(self.transform(tsplice).asInstanceOf[tpd.Tree]) + // the cast is safe, since the UntypedSplice case is overridden in UntypedTreeMap. + case _ => super.transform(tree) + } + } + cpy.UntypedSplice(tree)(untpdMap.transform(usplice)) + case _ if ctx.reporter.errorsReported => tree + } } - abstract class TreeAccumulator[X] { + abstract class TreeAccumulator[X] { self => // Ties the knot of the traversal: call `foldOver(x, tree))` to dive in the `tree` node. def apply(x: X, tree: Tree)(implicit ctx: Context): X @@ -1355,14 +1382,29 @@ object Trees { this(this(x, arg), annot) case Thicket(ts) => this(x, ts) - case _ if ctx.reporter.errorsReported || ctx.mode.is(Mode.Interactive) => - // In interactive mode, errors might come from previous runs. - // In case of errors it may be that typed trees point to untyped ones. - // The IDE can still traverse inside such trees, either in the run where errors - // are reported, or in subsequent ones. - x + case _ => + foldMoreCases(x, tree) } } + + def foldMoreCases(x: X, tree: Tree)(implicit ctx: Context): X = tree match { + case tpd.UntypedSplice(usplice) => + // For a typed tree accumulator: skip the untyped part and fold all typed splices. + // The case is overridden in UntypedTreeAccumulator. + val untpdAcc = new untpd.UntypedTreeAccumulator[X] { + override def apply(x: X, tree: untpd.Tree)(implicit ctx: Context): X = tree match { + case untpd.TypedSplice(tsplice) => self(x, tsplice) + case _ => foldOver(x, tree) + } + } + untpdAcc(x, usplice) + case _ if ctx.reporter.errorsReported || ctx.mode.is(Mode.Interactive) => + // In interactive mode, errors might come from previous runs. + // In case of errors it may be that typed trees point to untyped ones. + // The IDE can still traverse inside such trees, either in the run where errors + // are reported, or in subsequent ones. + x + } } abstract class TreeTraverser extends TreeAccumulator[Unit] { diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 440266d32f3e..1facbdfc6690 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -21,6 +21,8 @@ import scala.io.Codec /** Some creators for typed trees */ object tpd extends Trees.Instance[Type] with TypedTreeInfo { + case class UntypedSplice(splice: untpd.Tree) extends Tree + private def ta(implicit ctx: Context) = ctx.typeAssigner def Ident(tp: NamedType)(implicit ctx: Context): Ident = @@ -249,8 +251,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { val constr = firstParent.decl(nme.CONSTRUCTOR).suchThat(constr => isApplicable(constr.info)) New(firstParent, constr.symbol.asTerm, superArgs) } - val parents = superRef :: otherParents.map(TypeTree(_)) + ClassDefWithParents(cls, constr, superRef :: otherParents.map(TypeTree(_)), body) + } + def ClassDefWithParents(cls: ClassSymbol, constr: DefDef, parents: List[Tree], body: List[Tree])(implicit ctx: Context): TypeDef = { val selfType = if (cls.classInfo.selfInfo ne NoType) ValDef(ctx.newSelfSym(cls)) else EmptyValDef @@ -325,7 +329,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { case pre: ThisType => tp.isType || pre.cls.isStaticOwner || - tp.symbol.isParamOrAccessor && !pre.cls.is(Trait) && ctx.owner.enclosingClass == pre.cls + tp.symbol.isParamOrAccessor && !pre.cls.is(Trait) && ctx.owner.enclosingClass == pre.cls // was ctx.owner.enclosingClass.derivesFrom(pre.cls) which was not tight enough // and was spuriously triggered in case inner class would inherit from outer one // eg anonymous TypeMap inside TypeMap.andThen @@ -659,7 +663,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { override def skipTransform(tree: Tree)(implicit ctx: Context) = tree.tpe.isError - implicit class TreeOps[ThisTree <: tpd.Tree](val tree: ThisTree) extends AnyVal { + implicit class TreeOps[ThisTree <: tpd.Tree](private val tree: ThisTree) extends AnyVal { def isValue(implicit ctx: Context): Boolean = tree.isTerm && tree.tpe.widen.isValueType @@ -1041,14 +1045,27 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { else (trees.head.tpe eq trees1.head.tpe) && sameTypes(trees.tail, trees1.tail) } - def evalOnce(tree: Tree)(within: Tree => Tree)(implicit ctx: Context) = { - if (isIdempotentExpr(tree)) within(tree) + /** If `tree`'s purity level is less than `level`, let-bind it so that it gets evaluated + * only once. I.e. produce a + * + * { val x = 'tree ; ~within('x) } + * + * instead of otherwise + * + * ~within('tree) + */ + def letBindUnless(level: TreeInfo.PurityLevel, tree: Tree)(within: Tree => Tree)(implicit ctx: Context) = { + if (exprPurity(tree) >= level) within(tree) else { val vdef = SyntheticValDef(TempResultName.fresh(), tree) Block(vdef :: Nil, within(Ident(vdef.namedType))) } } + /** Let bind `tree` unless `tree` is at least idempotent */ + def evalOnce(tree: Tree)(within: Tree => Tree)(implicit ctx: Context) = + letBindUnless(TreeInfo.Idempotent, tree)(within) + def runtimeCall(name: TermName, args: List[Tree])(implicit ctx: Context): Tree = { Ident(defn.ScalaRuntimeModule.requiredMethod(name).termRef).appliedToArgs(args) } @@ -1067,9 +1084,17 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { override def inlineContext(call: Tree)(implicit ctx: Context): Context = ctx.fresh.setProperty(InlinedCalls, call :: enclosingInlineds) - /** All enclosing calls that are currently inlined, from innermost to outermost */ - def enclosingInlineds(implicit ctx: Context): List[Tree] = - ctx.property(InlinedCalls).getOrElse(Nil) + /** All enclosing calls that are currently inlined, from innermost to outermost. + * EmptyTree calls cancel the next-enclosing non-empty call in the list + */ + def enclosingInlineds(implicit ctx: Context): List[Tree] = { + def normalize(ts: List[Tree]): List[Tree] = ts match { + case t :: (ts1 @ (t1 :: ts2)) if t.isEmpty => normalize(if (t1.isEmpty) ts1 else ts2) + case t :: ts1 => t :: normalize(ts1) + case Nil => Nil + } + normalize(ctx.property(InlinedCalls).getOrElse(Nil)) + } /** The source file where the symbol of the `inline` method referred to by `call` * is defined diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 663f4393ad67..b663a827679c 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -21,11 +21,11 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { override def isType = op.name.isTypeName } - /** A typed subtree of an untyped tree needs to be wrapped in a TypedSlice + /** A typed subtree of an untyped tree needs to be wrapped in a TypedSplice * @param owner The current owner at the time the tree was defined */ - abstract case class TypedSplice(tree: tpd.Tree)(val owner: Symbol) extends ProxyTree { - def forwardTo = tree + abstract case class TypedSplice(splice: tpd.Tree)(val owner: Symbol) extends ProxyTree { + def forwardTo = splice } object TypedSplice { @@ -130,8 +130,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Lazy() extends Mod(Flags.Lazy) - case class Inline() extends Mod(Flags.Inline) - case class Transparent() extends Mod(Flags.Transparent) case class Enum() extends Mod(Flags.Enum) @@ -303,20 +301,23 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { // ------ Additional creation methods for untyped only ----------------- - /** new pre.C[Ts](args1)...(args_n) + /** new T(args1)...(args_n) * ==> - * (new pre.C).[Ts](args1)...(args_n) + * new T.[Ts](args1)...(args_n) + * + * where `Ts` are the class type arguments of `T` or its class type alias. + * Note: we also keep any type arguments as parts of `T`. This is necessary to allow + * navigation into these arguments from the IDE, and to do the right thing in + * PrepareTransparent. */ def New(tpt: Tree, argss: List[List[Tree]])(implicit ctx: Context): Tree = { val (tycon, targs) = tpt match { case AppliedTypeTree(tycon, targs) => (tycon, targs) case TypedSplice(tpt1: tpd.Tree) => - val tp = tpt1.tpe.dealias - val tycon = tp.typeConstructor - val argTypes = tp.argTypesLo - def wrap(tpe: Type) = TypeTree(tpe) withPos tpt.pos - (wrap(tycon), argTypes map wrap) + val argTypes = tpt1.tpe.dealias.argTypesLo + def wrap(tpe: Type) = TypeTree(tpe).withPos(tpt.pos) + (tpt, argTypes.map(wrap)) case _ => (tpt, Nil) } @@ -495,10 +496,14 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case tree: PatDef if (mods eq tree.mods) && (pats eq tree.pats) && (tpt eq tree.tpt) && (rhs eq tree.rhs) => tree case _ => finalize(tree, untpd.PatDef(mods, pats, tpt, rhs)) } + def TypedSplice(tree: Tree)(splice: tpd.Tree)(implicit ctx: Context) = tree match { + case tree: TypedSplice if splice `eq` tree.splice => tree + case _ => finalize(tree, untpd.TypedSplice(splice)) + } } abstract class UntypedTreeMap(cpy: UntypedTreeCopier = untpd.cpy) extends TreeMap(cpy) { - override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { + override def transformMoreCases(tree: Tree)(implicit ctx: Context): Tree = tree match { case ModuleDef(name, impl) => cpy.ModuleDef(tree)(name, transformSub(impl)) case ParsedTry(expr, handler, finalizer) => @@ -539,15 +544,17 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { cpy.ContextBounds(tree)(transformSub(bounds), transform(cxBounds)) case PatDef(mods, pats, tpt, rhs) => cpy.PatDef(tree)(mods, transform(pats), transform(tpt), transform(rhs)) + case tpd.UntypedSplice(splice) => + cpy.UntypedSplice(tree)(transform(splice)) case TypedSplice(_) => tree case _ => - super.transform(tree) + super.transformMoreCases(tree) } } - abstract class UntypedTreeAccumulator[X] extends TreeAccumulator[X] { - override def foldOver(x: X, tree: Tree)(implicit ctx: Context): X = tree match { + abstract class UntypedTreeAccumulator[X] extends TreeAccumulator[X] { self => + override def foldMoreCases(x: X, tree: Tree)(implicit ctx: Context): X = tree match { case ModuleDef(name, impl) => this(x, impl) case ParsedTry(expr, handler, finalizer) => @@ -588,10 +595,12 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { this(this(x, bounds), cxBounds) case PatDef(mods, pats, tpt, rhs) => this(this(this(x, pats), tpt), rhs) - case TypedSplice(tree) => - this(x, tree) + case TypedSplice(splice) => + this(x, splice) + case tpd.UntypedSplice(splice) => + this(x, splice) case _ => - super.foldOver(x, tree) + super.foldMoreCases(x, tree) } } diff --git a/compiler/src/dotty/tools/dotc/core/Annotations.scala b/compiler/src/dotty/tools/dotc/core/Annotations.scala index 3d66c876f6b7..ade035224780 100644 --- a/compiler/src/dotty/tools/dotc/core/Annotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Annotations.scala @@ -57,7 +57,7 @@ object Annotations { } /** An annotation indicating the body of a right-hand side, - * typically of an inline method. Treated specially in + * typically of a transparent method. Treated specially in * pickling/unpickling and TypeTreeMaps */ abstract class BodyAnnotation extends Annotation { diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index e4009c863738..48c842a284f5 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -158,7 +158,7 @@ class CheckRealizable(implicit ctx: Context) { private def memberRealizability(tp: Type) = { def checkField(sofar: Realizability, fld: SingleDenotation): Realizability = sofar andAlso { - if (checkedFields.contains(fld.symbol) || fld.symbol.is(Private | Mutable | Lazy | Erased)) + if (checkedFields.contains(fld.symbol) || fld.symbol.is(Private | Mutable | LateInitialized)) // if field is private it cannot be part of a visible path // if field is mutable it cannot be part of a path // if field is lazy or erased it does not need to be initialized when the owning object is diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index 1417d15087a1..5da5cc8bd9e0 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -176,7 +176,10 @@ object Decorators { } implicit class genericDeco[T](val x: T) extends AnyVal { - def reporting(op: T => String): T = { println(op(x)); x } + def reporting(op: T => String, printer: config.Printers.Printer = config.Printers.default): T = { + printer.println(op(x)) + x + } def assertingErrorsReported(implicit ctx: Context): T = { assert(ctx.reporter.errorsReported) x diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 357d6a60b2be..d8e9ebef4258 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -333,6 +333,10 @@ class Definitions { def NullType = NullClass.typeRef lazy val RuntimeNullModuleRef = ctx.requiredModuleRef("scala.runtime.Null") + lazy val ImplicitScrutineeTypeSym = + newSymbol(ScalaPackageClass, tpnme.IMPLICITkw, EmptyFlags, TypeBounds.empty).entered + def ImplicitScrutineeTypeRef: TypeRef = ImplicitScrutineeTypeSym.typeRef + lazy val ScalaPredefModuleRef = ctx.requiredModuleRef("scala.Predef") def ScalaPredefModule(implicit ctx: Context) = ScalaPredefModuleRef.symbol @@ -730,8 +734,8 @@ class Definitions { def ImplicitNotFoundAnnot(implicit ctx: Context) = ImplicitNotFoundAnnotType.symbol.asClass lazy val ForceInlineAnnotType = ctx.requiredClassRef("scala.forceInline") def ForceInlineAnnot(implicit ctx: Context) = ForceInlineAnnotType.symbol.asClass - lazy val InlineParamAnnotType = ctx.requiredClassRef("scala.annotation.internal.InlineParam") - def InlineParamAnnot(implicit ctx: Context) = InlineParamAnnotType.symbol.asClass + lazy val TransparentParamAnnotType = ctx.requiredClassRef("scala.annotation.internal.TransparentParam") + def TransparentParamAnnot(implicit ctx: Context) = TransparentParamAnnotType.symbol.asClass lazy val InvariantBetweenAnnotType = ctx.requiredClassRef("scala.annotation.internal.InvariantBetween") def InvariantBetweenAnnot(implicit ctx: Context) = InvariantBetweenAnnotType.symbol.asClass lazy val MigrationAnnotType = ctx.requiredClassRef("scala.annotation.migration") @@ -1213,7 +1217,7 @@ class Definitions { /** Lists core methods that don't have underlying bytecode, but are synthesized on-the-fly in every reflection universe */ lazy val syntheticCoreMethods = - AnyMethods ++ ObjectMethods ++ List(String_+, throwMethod) + AnyMethods ++ ObjectMethods ++ List(String_+, throwMethod, ImplicitScrutineeTypeSym) lazy val reservedScalaClassNames: Set[Name] = syntheticScalaClasses.map(_.name).toSet diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 2fc48608dfe6..399685e67f96 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -326,8 +326,8 @@ object Flags { /** A method that has default params */ final val DefaultParameterized = termFlag(27, "") - /** Symbol is inlined */ - final val Inline = commonFlag(29, "inline") + /** Labelled with `transparent` modifier */ + final val Transparent = commonFlag(29, "transparent") /** Symbol is defined by a Java class */ final val JavaDefined = commonFlag(30, "") @@ -351,9 +351,6 @@ object Flags { /** A bridge method. Set by Erasure */ final val Bridge = termFlag(34, "") - /** Symbol is a Java varargs bridge */ // (needed?) - final val VBridge = termFlag(35, "") // TODO remove - /** Symbol is a method which should be marked ACC_SYNCHRONIZED */ final val Synchronized = termFlag(36, "") @@ -363,9 +360,6 @@ object Flags { /** Symbol is a Java default method */ final val DefaultMethod = termFlag(38, "") - /** Labelled with `transparent` modifier */ - final val Transparent = termFlag(39, "transparent") - /** Symbol is an enum class or enum case (if used with case) */ final val Enum = commonFlag(40, "") @@ -439,7 +433,7 @@ object Flags { /** Flags representing source modifiers */ final val SourceModifierFlags = - commonFlags(Private, Protected, Abstract, Final, Inline, Transparent, + commonFlags(Private, Protected, Abstract, Final, Transparent, Sealed, Case, Implicit, Override, AbsOverride, Lazy, JavaStatic, Erased) /** Flags representing modifiers that can appear in trees */ @@ -460,7 +454,7 @@ object Flags { Scala2ExistentialCommon | Mutable.toCommonFlags | Touched | JavaStatic | CovariantOrOuter | ContravariantOrLabel | CaseAccessor.toCommonFlags | NonMember | ImplicitCommon | Permanent | Synthetic | - SuperAccessorOrScala2x | Inline | Transparent.toCommonFlags + SuperAccessorOrScala2x | Transparent /** Flags that are not (re)set when completing the denotation, or, if symbol is * a top-level class or object, when completing the denotation once the class @@ -551,8 +545,11 @@ object Flags { /** Either method or lazy or deferred */ final val MethodOrLazyOrDeferred = Method | Lazy | Deferred - /** Labeled `private`, `final`, `inline`, or `transparent` */ - final val EffectivelyFinal = Private | Final | Inline | Transparent.toCommonFlags + /** Assumed to be pure */ + final val StableOrErased = Stable | Erased + + /** Labeled `private`, `final`, or `transparent` */ + final val EffectivelyFinal = Private | Final | Transparent /** A private method */ final val PrivateMethod = allOf(Private, Method) @@ -560,14 +557,11 @@ object Flags { /** A private accessor */ final val PrivateAccessor = allOf(Private, Accessor) - /** An inline method */ - final val InlineMethod = allOf(Inline, Method) - - /** An transparent method */ + /** A transparent method */ final val TransparentMethod = allOf(Transparent, Method) - /** An inline parameter */ - final val InlineParam = allOf(Inline, Param) + /** A transparent parameter */ + final val TransparentParam = allOf(Transparent, Param) /** An enum case */ final val EnumCase = allOf(Enum, Case) @@ -593,8 +587,8 @@ object Flags { /** A deferred type member or parameter (these don't have right hand sides) */ final val DeferredOrTypeParam = Deferred | TypeParam - /** value that's final, inline, or transparent */ - final val FinalOrInlineOrTransparent = Final | Inline | Transparent.toCommonFlags + /** value that's final or transparent */ + final val FinalOrTransparent = Final | Transparent /** A covariant type parameter instance */ final val LocalCovariant = allOf(Local, Covariant) diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index e689544503db..d05191dc024b 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -95,7 +95,9 @@ object Mode { /** We are in the IDE */ val Interactive = newMode(20, "Interactive") - /** Read comments from definitions when unpickling from TASTY */ - val ReadComments = newMode(21, "ReadComments") + /** We are typing the body of a transparent method */ + val TransparentBody = newMode(21, "TransparentBody") + /** Read comments from definitions when unpickling from TASTY */ + val ReadComments = newMode(22, "ReadComments") } diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index a9f841c33697..4e1494f28ed8 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -275,23 +275,25 @@ object NameKinds { } /** Other unique names */ - val TempResultName = new UniqueNameKind("ev$") - val EvidenceParamName = new UniqueNameKind("evidence$") - val DepParamName = new UniqueNameKind("(param)") - val LazyImplicitName = new UniqueNameKind("$_lazy_implicit_$") - val LazyLocalName = new UniqueNameKind("$lzy") - val LazyLocalInitName = new UniqueNameKind("$lzyINIT") - val LazyFieldOffsetName = new UniqueNameKind("$OFFSET") - val LazyBitMapName = new UniqueNameKind(nme.BITMAP_PREFIX.toString) - val NonLocalReturnKeyName = new UniqueNameKind("nonLocalReturnKey") - val WildcardParamName = new UniqueNameKind("_$") - val TailLabelName = new UniqueNameKind("tailLabel") - val ExceptionBinderName = new UniqueNameKind("ex") - val SkolemName = new UniqueNameKind("?") - val LiftedTreeName = new UniqueNameKind("liftedTree") - val SuperArgName = new UniqueNameKind("$superArg$") - val DocArtifactName = new UniqueNameKind("$doc") - val UniqueInlineName = new UniqueNameKind("$i") + val TempResultName = new UniqueNameKind("ev$") + val EvidenceParamName = new UniqueNameKind("evidence$") + val DepParamName = new UniqueNameKind("(param)") + val LazyImplicitName = new UniqueNameKind("$_lazy_implicit_$") + val LazyLocalName = new UniqueNameKind("$lzy") + val LazyLocalInitName = new UniqueNameKind("$lzyINIT") + val LazyFieldOffsetName = new UniqueNameKind("$OFFSET") + val LazyBitMapName = new UniqueNameKind(nme.BITMAP_PREFIX.toString) + val NonLocalReturnKeyName = new UniqueNameKind("nonLocalReturnKey") + val WildcardParamName = new UniqueNameKind("_$") + val TailLabelName = new UniqueNameKind("tailLabel") + val ExceptionBinderName = new UniqueNameKind("ex") + val SkolemName = new UniqueNameKind("?") + val LiftedTreeName = new UniqueNameKind("liftedTree") + val SuperArgName = new UniqueNameKind("$superArg$") + val DocArtifactName = new UniqueNameKind("$doc") + val UniqueInlineName = new UniqueNameKind("$i") + val TransparentScrutineeName = new UniqueNameKind("$scrutinee") + val TransparentBinderName = new UniqueNameKind("$elem") /** A kind of unique extension methods; Unlike other unique names, these can be * unmangled. diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 171cb189ccf9..f64980e6c9a2 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -600,7 +600,7 @@ object SymDenotations { /** Is this a denotation of a stable term (or an arbitrary type)? */ final def isStable(implicit ctx: Context) = - isType || is(Stable) || !(is(UnstableValue) || info.isInstanceOf[ExprType]) + isType || is(StableOrErased) || !is(UnstableValue) && !info.isInstanceOf[ExprType] /** Is this a denotation of a class that does not have - either direct or inherited - * initaliazion code? @@ -788,13 +788,14 @@ object SymDenotations { def isSkolem: Boolean = name == nme.SKOLEM - def isInlinedMethod(implicit ctx: Context): Boolean = - is(InlineMethod, butNot = Accessor) - def isTransparentMethod(implicit ctx: Context): Boolean = - is(TransparentMethod, butNot = Accessor) + is(TransparentMethod, butNot = AccessorOrSynthetic) - def isInlineableMethod(implicit ctx: Context) = isInlinedMethod || isTransparentMethod + /** A transparent method that is not nested inside another transparent method. + * Nested transparents are not inlineable yet, only their inlined copies are. + */ + def isTransparentInlineable(implicit ctx: Context): Boolean = + isTransparentMethod && !owner.ownersIterator.exists(_.is(TransparentMethod)) /** ()T and => T types should be treated as equivalent for this symbol. * Note: For the moment, we treat Scala-2 compiled symbols as loose matching, diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 40ff5b0dfb19..f00ca26f215e 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -397,6 +397,8 @@ trait Symbols { this: Context => def requiredMethod(path: PreName): TermSymbol = base.staticRef(path.toTermName).requiredSymbol(_ is Method).asTerm + + def requiredMethodRef(path: PreName): TermRef = requiredMethod(path).termRef } object Symbols { @@ -544,11 +546,29 @@ object Symbols { if (this is Module) this.moduleClass.validFor |= InitialPeriod } else this.owner.asClass.ensureFreshScopeAfter(phase) - if (!isPrivate) - assert(phase.changesMembers, i"$this entered in ${this.owner} at undeclared phase $phase") + assert(isPrivate || phase.changesMembers, i"$this entered in ${this.owner} at undeclared phase $phase") entered } + /** Remove symbol from scope of owning class */ + final def drop()(implicit ctx: Context): Unit = { + this.owner.asClass.delete(this) + if (this is Module) this.owner.asClass.delete(this.moduleClass) + } + + /** Remove symbol from scope of owning class after given `phase`. Create a fresh + * denotation for its owner class if the class has not yet already one that starts being valid after `phase`. + * @pre Symbol is a class member + */ + def dropAfter(phase: DenotTransformer)(implicit ctx: Context): Unit = + if (ctx.phaseId != phase.next.id) dropAfter(phase)(ctx.withPhase(phase.next)) + else { + assert (!this.owner.is(Package)) + this.owner.asClass.ensureFreshScopeAfter(phase) + assert(isPrivate || phase.changesMembers, i"$this deleted in ${this.owner} at undeclared phase $phase") + drop() + } + /** This symbol, if it exists, otherwise the result of evaluating `that` */ def orElse(that: => Symbol)(implicit ctx: Context) = if (this.exists) this else that diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 05fd66b54282..ce039351201f 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -271,6 +271,9 @@ trait TypeOps { this: Context => // TODO: Make standalone object. violations.toList } + /** Are we in a transparent method body? */ + def inTransparentMethod = owner.ownersIterator.exists(_.isTransparentMethod) + /** Is `feature` enabled in class `owner`? * This is the case if one of the following two alternatives holds: * diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 2040cc6377d9..9b293c01791a 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2969,18 +2969,18 @@ object Types { abstract class MethodTypeCompanion extends TermLambdaCompanion[MethodType] { self => /** Produce method type from parameter symbols, with special mappings for repeated - * and inline parameters: + * and transparent parameters: * - replace @repeated annotations on Seq or Array types by types - * - add @inlineParam to inline call-by-value parameters + * - add @inlineParam to transparent call-by-value parameters */ def fromSymbols(params: List[Symbol], resultType: Type)(implicit ctx: Context) = { - def translateInline(tp: Type): Type = tp match { + def translateTransparent(tp: Type): Type = tp match { case _: ExprType => tp - case _ => AnnotatedType(tp, Annotation(defn.InlineParamAnnot)) + case _ => AnnotatedType(tp, Annotation(defn.TransparentParamAnnot)) } def paramInfo(param: Symbol) = { val paramType = param.info.annotatedToRepeated - if (param.is(Inline)) translateInline(paramType) else paramType + if (param.is(Transparent)) translateTransparent(paramType) else paramType } apply(params.map(_.name.asTermName))( diff --git a/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala index 7e25c80c942d..c3aa124928ae 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala @@ -13,7 +13,7 @@ import collection.mutable import TastyBuffer._ import util.Positions._ -class PositionPickler(pickler: TastyPickler, addrOfTree: tpd.Tree => Option[Addr]) { +class PositionPickler(pickler: TastyPickler, addrOfTree: untpd.Tree => Option[Addr]) { val buf = new TastyBuffer(5000) pickler.newSection("Positions", buf) import buf._ @@ -62,8 +62,8 @@ class PositionPickler(pickler: TastyPickler, addrOfTree: tpd.Tree => Option[Addr } def traverse(x: Any): Unit = x match { - case x: Tree @unchecked => - val pos = if (x.isInstanceOf[MemberDef]) x.pos else x.pos.toSynthetic + case x: untpd.Tree => + val pos = if (x.isInstanceOf[untpd.MemberDef]) x.pos else x.pos.toSynthetic if (pos.exists && (pos != x.initialPos.toSynthetic || alwaysNeedsPos(x))) { addrOfTree(x) match { case Some(addr) if !pickledIndices.contains(addr.index) => @@ -75,7 +75,7 @@ class PositionPickler(pickler: TastyPickler, addrOfTree: tpd.Tree => Option[Addr } //else if (x.pos.exists) println(i"skipping $x") x match { - case x: MemberDef @unchecked => + case x: untpd.MemberDef @unchecked => for (ann <- x.symbol.annotations) traverse(ann.tree) case _ => } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index f1237189459c..59dd7e4314a7 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -59,6 +59,7 @@ Standard-Section: "ASTs" TopLevelStat* DEFDEF Length NameRef TypeParam* Params* returnType_Term rhs_Term? Modifier* TYPEDEF Length NameRef (type_Term | Template) Modifier* + OBJECTDEF Length NameRef Template Modifier* IMPORT Length qual_Term Selector* Selector = IMPORTED name_NameRef RENAMED to_NameRef @@ -109,6 +110,7 @@ Standard-Section: "ASTs" TopLevelStat* EMPTYTREE SHAREDterm term_ASTRef HOLE Length idx_Nat arg_Tree* + UNTYPEDSPLICE Length splice_TermUntyped splice_Type CaseDef = CASEDEF Length pat_Term rhs_Tree guard_Tree? ImplicitArg = IMPLICITARG arg_Term @@ -179,9 +181,9 @@ Standard-Section: "ASTs" TopLevelStat* ERASED LAZY OVERRIDE - INLINE TRANSPARENT - MACRO // inline method containing toplevel splices + TYPELEVEL // transparent method containing toplevel matches (TODO: merge with MACRO) + MACRO // transparent method containing toplevel splices STATIC // mapped to static Java member OBJECT // an object or its class TRAIT // a trait @@ -203,6 +205,15 @@ Standard-Section: "ASTs" TopLevelStat* Annotation = ANNOTATION Length tycon_Type fullAnnotation_Term +// --------------- untyped additions ------------------------------------------ + + TermUntyped = Term + TYPEDSPLICE Length splice_Term + FUNCTION Length body_Term arg_Term* + INFIXOP Length op_NameRef left_Term right_Term + PATDEF Length type_Term rhs_Term pattern_Term* Modifier* + EMPTYTYPETREE + Note: Tree tags are grouped into 5 categories that determine what follows, and thus allow to compute the size of the tagged tree in a generic way. Category 1 (tags 1-49) : tag @@ -233,7 +244,7 @@ Standard Section: "Comments" Comment* object TastyFormat { final val header = Array(0x5C, 0xA1, 0xAB, 0x1F) - val MajorVersion = 9 + val MajorVersion = 10 val MinorVersion = 0 /** Tags used to serialize names */ @@ -286,8 +297,8 @@ object TastyFormat { final val IMPLICIT = 13 final val LAZY = 14 final val OVERRIDE = 15 - final val INLINE = 16 - final val TRANSPARENT = 17 + final val TRANSPARENT = 16 + final val STATIC = 18 final val OBJECT = 19 final val TRAIT = 20 @@ -307,6 +318,8 @@ object TastyFormat { final val MACRO = 34 final val ERASED = 35 final val PARAMsetter = 36 + final val EMPTYTREE = 37 + final val EMPTYTYPETREE = 38 // Cat. 2: tag Nat @@ -408,6 +421,7 @@ object TastyFormat { final val ANNOTATION = 172 final val TERMREFin = 173 final val TYPEREFin = 174 + final val OBJECTDEF = 175 // In binary: 101100EI // I = implicit method type @@ -417,6 +431,14 @@ object TastyFormat { final val ERASEDMETHODtype = 178 final val ERASEDIMPLICITMETHODtype = 179 + final val UNTYPEDSPLICE = 199 + + // Tags for untyped trees only: + final val TYPEDSPLICE = 200 + final val FUNCTION = 201 + final val INFIXOP = 202 + final val PATDEF = 203 + def methodType(isImplicit: Boolean = false, isErased: Boolean = false) = { val implicitOffset = if (isImplicit) 1 else 0 val erasedOffset = if (isErased) 2 else 0 @@ -432,7 +454,7 @@ object TastyFormat { /** Useful for debugging */ def isLegalTag(tag: Int) = - firstSimpleTreeTag <= tag && tag <= PARAMsetter || + firstSimpleTreeTag <= tag && tag <= EMPTYTYPETREE || firstNatTreeTag <= tag && tag <= SYMBOLconst || firstASTTreeTag <= tag && tag <= SINGLETONtpt || firstNatASTTreeTag <= tag && tag <= NAMEDARG || @@ -453,7 +475,6 @@ object TastyFormat { | ERASED | LAZY | OVERRIDE - | INLINE | TRANSPARENT | MACRO | STATIC @@ -510,7 +531,6 @@ object TastyFormat { case ERASED => "ERASED" case LAZY => "LAZY" case OVERRIDE => "OVERRIDE" - case INLINE => "INLINE" case TRANSPARENT => "TRANSPARENT" case MACRO => "MACRO" case STATIC => "STATIC" @@ -529,6 +549,8 @@ object TastyFormat { case DEFAULTparameterized => "DEFAULTparameterized" case STABLE => "STABLE" case PARAMsetter => "PARAMsetter" + case EMPTYTREE => "EMPTYTREE" + case EMPTYTYPETREE => "EMPTYTYPETREE" case SHAREDterm => "SHAREDterm" case SHAREDtype => "SHAREDtype" @@ -560,6 +582,7 @@ object TastyFormat { case VALDEF => "VALDEF" case DEFDEF => "DEFDEF" case TYPEDEF => "TYPEDEF" + case OBJECTDEF => "OBJECTDEF" case IMPORT => "IMPORT" case TYPEPARAM => "TYPEPARAM" case PARAMS => "PARAMS" @@ -627,13 +650,19 @@ object TastyFormat { case PRIVATEqualified => "PRIVATEqualified" case PROTECTEDqualified => "PROTECTEDqualified" case HOLE => "HOLE" + + case UNTYPEDSPLICE => "UNTYPEDSPLICE" + case TYPEDSPLICE => "TYPEDSPLICE" + case FUNCTION => "FUNCTION" + case INFIXOP => "INFIXOP" + case PATDEF => "PATDEF" } /** @return If non-negative, the number of leading references (represented as nats) of a length/trees entry. * If negative, minus the number of leading non-reference trees. */ def numRefs(tag: Int) = tag match { - case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | NAMEDARG | RETURN | BIND | + case VALDEF | DEFDEF | TYPEDEF | OBJECTDEF | TYPEPARAM | PARAM | NAMEDARG | RETURN | BIND | SELFDEF | REFINEDtype | TERMREFin | TYPEREFin | HOLE => 1 case RENAMED | PARAMtype => 2 case POLYtype | METHODtype | TYPELAMBDAtype => -1 diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala index 3a0442d7cbc6..80a7aac1743b 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala @@ -64,7 +64,7 @@ class TastyPrinter(bytes: Array[Byte])(implicit ctx: Context) { tag match { case RENAMED => printName(); printName() - case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | NAMEDARG | BIND => + case VALDEF | DEFDEF | TYPEDEF | OBJECTDEF | TYPEPARAM | PARAM | NAMEDARG | BIND => printName(); printTrees() case REFINEDtype | TERMREFin | TYPEREFin => printName(); printTree(); printTrees() diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index c2b22ade4ed6..f4f202650877 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -4,7 +4,7 @@ package core package tasty import ast.Trees._ -import ast.{untpd, tpd} +import ast.{untpd, tpd, desugar} import TastyFormat._ import Contexts._, Symbols._, Types._, Names._, Constants._, Decorators._, Annotations._, StdNames.tpnme, NameOps._ import collection.mutable @@ -536,7 +536,7 @@ class TreePickler(pickler: TastyPickler) { pickleTree(tp) case Annotated(tree, annot) => writeByte(ANNOTATEDtpt) - withLength { pickleTree(tree); pickleTree(annot.tree) } + withLength { pickleTree(tree); pickleTree(annot) } case LambdaTypeTree(tparams, body) => writeByte(LAMBDAtpt) withLength { pickleParams(tparams); pickleTree(body) } @@ -546,6 +546,11 @@ class TreePickler(pickler: TastyPickler) { pickleTree(lo); if (hi ne lo) pickleTree(hi) } + case EmptyTree => + writeByte(EMPTYTREE) + case tpd.UntypedSplice(splice) => + writeByte(UNTYPEDSPLICE) + withLength { pickleUntyped(splice); pickleType(tree.tpe) } case Hole(idx, args) => writeByte(HOLE) withLength { @@ -577,18 +582,26 @@ class TreePickler(pickler: TastyPickler) { def pickleModifiers(sym: Symbol)(implicit ctx: Context): Unit = { import Flags._ - val flags = sym.flags + var flags = sym.flags val privateWithin = sym.privateWithin if (privateWithin.exists) { writeByte(if (flags is Protected) PROTECTEDqualified else PRIVATEqualified) pickleType(privateWithin.typeRef) + flags = flags &~ Protected } + if ((flags is ParamAccessor) && sym.isTerm && !sym.isSetter) + flags = flags &~ ParamAccessor // we only generate a tag for parameter setters + pickleFlags(flags, sym.isTerm) + sym.annotations.foreach(pickleAnnotation(sym, _)) + } + + def pickleFlags(flags: Flags.FlagSet, isTerm: Boolean)(implicit ctx: Context): Unit = { + import Flags._ if (flags is Private) writeByte(PRIVATE) - if (flags is Protected) if (!privateWithin.exists) writeByte(PROTECTED) - if ((flags is Final) && !(sym is Module)) writeByte(FINAL) + if (flags is Protected) writeByte(PROTECTED) + if (flags.is(Final, butNot = Module)) writeByte(FINAL) if (flags is Case) writeByte(CASE) if (flags is Override) writeByte(OVERRIDE) - if (flags is Inline) writeByte(INLINE) if (flags is Transparent) writeByte(TRANSPARENT) if (flags is Macro) writeByte(MACRO) if (flags is JavaStatic) writeByte(STATIC) @@ -598,7 +611,7 @@ class TreePickler(pickler: TastyPickler) { if (flags is Synthetic) writeByte(SYNTHETIC) if (flags is Artifact) writeByte(ARTIFACT) if (flags is Scala2x) writeByte(SCALA2X) - if (sym.isTerm) { + if (isTerm) { if (flags is Implicit) writeByte(IMPLICIT) if (flags is Erased) writeByte(ERASED) if (flags.is(Lazy, butNot = Module)) writeByte(LAZY) @@ -608,7 +621,7 @@ class TreePickler(pickler: TastyPickler) { if (flags is CaseAccessor) writeByte(CASEaccessor) if (flags is DefaultParameterized) writeByte(DEFAULTparameterized) if (flags is Stable) writeByte(STABLE) - if ((flags is ParamAccessor) && sym.isSetter) writeByte(PARAMsetter) + if (flags is ParamAccessor) writeByte(PARAMsetter) if (flags is Label) writeByte(LABEL) } else { if (flags is Sealed) writeByte(SEALED) @@ -617,7 +630,6 @@ class TreePickler(pickler: TastyPickler) { if (flags is Covariant) writeByte(COVARIANT) if (flags is Contravariant) writeByte(CONTRAVARIANT) } - sym.annotations.foreach(pickleAnnotation(sym, _)) } private def isUnpicklable(owner: Symbol, ann: Annotation)(implicit ctx: Context) = ann match { @@ -626,8 +638,7 @@ class TreePickler(pickler: TastyPickler) { // a different toplevel class, it is impossible to pickle a reference to it. // Such annotations will be reconstituted when unpickling the child class. // See tests/pickling/i3149.scala - case _ => ann.symbol == defn.BodyAnnot - // inline bodies are reconstituted automatically when unpickling + case _ => false } def pickleAnnotation(owner: Symbol, ann: Annotation)(implicit ctx: Context) = @@ -636,6 +647,233 @@ class TreePickler(pickler: TastyPickler) { withLength { pickleType(ann.symbol.typeRef); pickleTree(ann.tree) } } +// ---- pickling untyped trees ---------------------------------- + + def pickleUntyped(tree: untpd.Tree)(implicit ctx: Context): Unit = { + + def pickleDummyRef(): Unit = writeNat(0) + + def pickleDummyType(): Unit = writeByte(EMPTYTYPETREE) + + def pickleUnlessEmpty(tree: untpd.Tree): Unit = + if (!tree.isEmpty) pickleUntyped(tree) + + def pickleTpt(tree: untpd.Tree) = pickleUntyped(tree)(ctx.addMode(Mode.Type)) + def pickleTerm(tree: untpd.Tree) = pickleUntyped(tree)(ctx.retractMode(Mode.Type)) + + def pickleAllParams(tree: untpd.DefDef): Unit = { + pickleParams(tree.tparams) + for (vparams <- tree.vparamss) { + writeByte(PARAMS) + withLength { pickleParams(vparams) } + } + } + + def pickleParams(trees: List[untpd.Tree]): Unit = + trees.foreach(pickleParam) + + def pickleParam(tree: untpd.Tree): Unit = tree match { + case tree: untpd.ValDef => pickleDef(PARAM, tree, tree.tpt) + case tree: untpd.DefDef => pickleDef(PARAM, tree, tree.tpt, tree.rhs) + case tree: untpd.TypeDef => pickleDef(TYPEPARAM, tree, tree.rhs) + } + + def pickleParent(tree: untpd.Tree): Unit = tree match { + case _: untpd.Apply | _: untpd.TypeApply => pickleUntyped(tree) + case _ => pickleTpt(tree) + } + + def pickleDef(tag: Int, tree: untpd.MemberDef, tpt: untpd.Tree, rhs: untpd.Tree = untpd.EmptyTree, pickleParams: => Unit = ()) = { + import untpd.modsDeco + writeByte(tag) + withLength { + pickleName(tree.name) + pickleParams + pickleTpt(tpt) + pickleUnlessEmpty(rhs) + pickleModifiers(tree.mods, tree.name.isTermName) + } + } + + def pickleModifiers(mods: untpd.Modifiers, isTerm: Boolean): Unit = { + import Flags._ + var flags = mods.flags + val privateWithin = mods.privateWithin + if (!privateWithin.isEmpty) { + writeByte(if (flags is Protected) PROTECTEDqualified else PRIVATEqualified) + pickleUntyped(untpd.Ident(privateWithin)) + flags = flags &~ Protected + } + pickleFlags(flags, isTerm) + mods.annotations.foreach(pickleAnnotation) + } + + def pickleAnnotation(annotTree: untpd.Tree) = { + writeByte(ANNOTATION) + withLength { pickleDummyType(); pickleUntyped(annotTree) } + } + + try tree match { + case Ident(name) => + writeByte(if (name.isTypeName) TYPEREF else TERMREF) + pickleName(name) + pickleDummyType() + case This(qual) => + writeByte(QUALTHIS) + pickleUntyped(qual) + case Select(qual, name) => + writeByte(if (name.isTypeName) SELECTtpt else SELECT) + pickleName(name) + if (qual.isType) pickleTpt(qual) else pickleTerm(qual) + case Apply(fun, args) => + writeByte(APPLY) + withLength { + pickleUntyped(fun) + args.foreach(pickleUntyped) + } + case untpd.Throw(exc) => + writeByte(THROW) + pickleUntyped(exc) + case TypeApply(fun, args) => + writeByte(TYPEAPPLY) + withLength { + pickleUntyped(fun) + args.foreach(pickleTpt) + } + case Literal(const) => + pickleConstant(const) + case Super(qual, mix) => + writeByte(SUPER) + withLength { + pickleUntyped(qual); + if (!mix.isEmpty) pickleUntyped(mix) + } + case New(tpt) => + writeByte(NEW) + pickleTpt(tpt) + case Typed(expr, tpt) => + writeByte(TYPED) + withLength { pickleUntyped(expr); pickleTpt(tpt) } + case NamedArg(name, arg) => + writeByte(NAMEDARG) + pickleName(name) + pickleUntyped(arg) + case Assign(lhs, rhs) => + writeByte(ASSIGN) + withLength { pickleUntyped(lhs); pickleUntyped(rhs) } + case Block(stats, expr) => + writeByte(BLOCK) + withLength { pickleUntyped(expr); stats.foreach(pickleUntyped) } + case If(cond, thenp, elsep) => + writeByte(IF) + withLength { pickleUntyped(cond); pickleUntyped(thenp); pickleUntyped(elsep) } + case Match(selector, cases) => + writeByte(MATCH) + withLength { pickleUntyped(selector); cases.foreach(pickleUntyped) } + case CaseDef(pat, guard, rhs) => + writeByte(CASEDEF) + withLength { pickleUntyped(pat); pickleUntyped(rhs); pickleUnlessEmpty(guard) } + case Return(expr, from) => + writeByte(RETURN) + withLength { pickleDummyRef(); pickleUnlessEmpty(expr) } + case Try(block, cases, finalizer) => + writeByte(TRY) + withLength { pickleUntyped(block); cases.foreach(pickleUntyped); pickleUnlessEmpty(finalizer) } + case Bind(name, body) => + writeByte(BIND) + withLength { + pickleName(name); pickleDummyType(); pickleUntyped(body) + } + case Alternative(alts) => + writeByte(ALTERNATIVE) + withLength { alts.foreach(pickleUntyped) } + case tree: untpd.ValDef => + pickleDef(VALDEF, tree, tree.tpt, tree.rhs) + case tree: untpd.DefDef => + pickleDef(DEFDEF, tree, tree.tpt, tree.rhs, pickleAllParams(tree)) + case tree: untpd.TypeDef => + pickleDef(TYPEDEF, tree, tree.rhs) + case tree: untpd.ModuleDef => + pickleDef(OBJECTDEF, tree, tree.impl) + case tree: untpd.Template => + writeByte(TEMPLATE) + withLength { + tree.parents.foreach(pickleParent) + if (!tree.self.isEmpty) { + writeByte(SELFDEF); pickleName(tree.self.name); pickleTpt(tree.self.tpt) + } + pickleUntyped(tree.constr) + tree.body.foreach(pickleUntyped) + } + case Import(expr, selectors) => + writeByte(IMPORT) + withLength { pickleUntyped(expr); pickleSelectors(selectors) } + case tree: untpd.TypeTree => + pickleDummyType() + case SingletonTypeTree(ref) => + writeByte(SINGLETONtpt) + pickleTerm(ref) + case RefinedTypeTree(parent, refinements) => + writeByte(REFINEDtpt) + withLength { pickleTpt(parent); refinements.foreach(pickleTerm) } + case AppliedTypeTree(tycon, args) => + writeByte(APPLIEDtpt) + withLength { pickleTpt(tycon); args.foreach(pickleTpt) } + case AndTypeTree(tp1, tp2) => + writeByte(ANDtpt) + withLength { pickleTpt(tp1); pickleTpt(tp2) } + case OrTypeTree(tp1, tp2) => + writeByte(ORtpt) + withLength { pickleTpt(tp1); pickleTpt(tp2) } + case ByNameTypeTree(tp) => + writeByte(BYNAMEtpt) + pickleTpt(tp) + case Annotated(tree, annot) => + writeByte(ANNOTATEDtpt) + withLength { pickleTpt(tree); pickleTerm(annot) } + case LambdaTypeTree(tparams, body) => + writeByte(LAMBDAtpt) + withLength { pickleParams(tparams); pickleTpt(body) } + case TypeBoundsTree(lo, hi) => + writeByte(TYPEBOUNDStpt) + withLength { + pickleTpt(lo); + if (hi ne lo) pickleTpt(hi) + } + case untpd.Function(args, body) => + writeByte(FUNCTION) + withLength { pickleUntyped(body); args.foreach(pickleUntyped) } + case untpd.InfixOp(l, op, r) => + writeByte(INFIXOP) + withLength { pickleUntyped(l); pickleUntyped(op); pickleUntyped(r) } + case untpd.PatDef(mods, pats, tpt, rhs) => + writeByte(PATDEF) + withLength { + pickleTpt(tpt) + pickleUntyped(rhs) + pats.foreach(pickleUntyped) + pickleModifiers(mods, isTerm = true) + } + case untpd.TypedSplice(splice) => + writeByte(TYPEDSPLICE) + withLength { pickleTree(splice) } + case Thicket(trees) => + if (trees.isEmpty) writeByte(EMPTYTREE) + else trees.foreach(pickleUntyped) + case _ => + val tree1 = desugar(tree) + assert(tree1 `ne` tree, s"Cannot pickle untyped tree $tree") + pickleUntyped(tree1) + } + catch { + case ex: AssertionError => + println(i"error when pickling tree $tree") + throw ex + } + } + +// ---- main entry points --------------------------------------- + def pickle(trees: List[Tree])(implicit ctx: Context) = { trees.foreach(tree => if (!tree.isEmpty) pickleTree(tree)) def missing = forwardSymRefs.keysIterator.map(_.showLocated).toList diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index cdd68445b46f..aafad008fef6 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -515,7 +515,7 @@ class TreeUnpickler(reader: TastyReader, val rhsStart = currentAddr val rhsIsEmpty = nothingButMods(end) if (!rhsIsEmpty) skipTree() - val (givenFlags, annotFns, privateWithin) = readModifiers(end) + val (givenFlags, annotFns, privateWithin) = readModifiers(end, readTypedAnnot, readTypedWithin, NoSymbol) pickling.println(i"creating symbol $name at $start with flags $givenFlags") val flags = normalizeFlags(tag, givenFlags, name, isAbsType, rhsIsEmpty) def adjustIfModule(completer: LazyType) = @@ -549,12 +549,6 @@ class TreeUnpickler(reader: TastyReader, sym.completer.withDecls(newScope) forkAt(templateStart).indexTemplateParams()(localContext(sym)) } - else if (sym.isInlineableMethod) - sym.addAnnotation(LazyBodyAnnotation { ctx0 => - implicit val ctx: Context = localContext(sym)(ctx0).addMode(Mode.ReadPositions) - // avoids space leaks by not capturing the current context - forkAt(rhsStart).readTerm() - }) goto(start) sym } @@ -562,10 +556,12 @@ class TreeUnpickler(reader: TastyReader, /** Read modifier list into triplet of flags, annotations and a privateWithin * boundary symbol. */ - def readModifiers(end: Addr)(implicit ctx: Context): (FlagSet, List[Symbol => Annotation], Symbol) = { + def readModifiers[WithinType, AnnotType] + (end: Addr, readAnnot: Context => Symbol => AnnotType, readWithin: Context => WithinType, defaultWithin: WithinType) + (implicit ctx: Context): (FlagSet, List[Symbol => AnnotType], WithinType) = { var flags: FlagSet = EmptyFlags - var annotFns: List[Symbol => Annotation] = Nil - var privateWithin: Symbol = NoSymbol + var annotFns: List[Symbol => AnnotType] = Nil + var privateWithin = defaultWithin while (currentAddr.index != end.index) { def addFlag(flag: FlagSet) = { flags |= flag @@ -588,7 +584,6 @@ class TreeUnpickler(reader: TastyReader, case ERASED => addFlag(Erased) case LAZY => addFlag(Lazy) case OVERRIDE => addFlag(Override) - case INLINE => addFlag(Inline) case TRANSPARENT => addFlag(Transparent) case MACRO => addFlag(Macro) case STATIC => addFlag(JavaStatic) @@ -611,10 +606,10 @@ class TreeUnpickler(reader: TastyReader, addFlag(ParamAccessor) case PRIVATEqualified => readByte() - privateWithin = readType().typeSymbol + privateWithin = readWithin(ctx) case PROTECTEDqualified => addFlag(Protected) - privateWithin = readType().typeSymbol + privateWithin = readWithin(ctx) case ANNOTATION => annotFns = readAnnot(ctx) :: annotFns case tag => @@ -624,7 +619,10 @@ class TreeUnpickler(reader: TastyReader, (flags, annotFns.reverse, privateWithin) } - private val readAnnot: Context => Symbol => Annotation = { + private val readTypedWithin: Context => Symbol = + implicit ctx => readType().typeSymbol + + private val readTypedAnnot: Context => Symbol => Annotation = { implicit ctx => readByte() val end = readEnd() @@ -632,9 +630,12 @@ class TreeUnpickler(reader: TastyReader, val lazyAnnotTree = readLaterWithOwner(end, rdr => ctx => rdr.readTerm()(ctx)) owner => - Annotation.deferredSymAndTree( - implicit ctx => tp.typeSymbol, - implicit ctx => lazyAnnotTree(owner).complete) + if (tp.isRef(defn.BodyAnnot)) + LazyBodyAnnotation(implicit ctx => lazyAnnotTree(owner).complete) + else + Annotation.deferredSymAndTree( + implicit ctx => tp.typeSymbol, + implicit ctx => lazyAnnotTree(owner).complete) } /** Create symbols for the definitions in the statement sequence between @@ -733,11 +734,6 @@ class TreeUnpickler(reader: TastyReader, def readRhs(implicit ctx: Context) = if (nothingButMods(end)) EmptyTree - else if (sym.isInlinedMethod) - // The body of an inline method is stored in an annotation, so no need to unpickle it again - new Trees.Lazy[Tree] { - def complete(implicit ctx: Context) = typer.Inliner.bodyToInline(sym) - } else readLater(end, rdr => ctx => rdr.readTerm()(ctx.retractMode(Mode.InSuperCall))) @@ -924,26 +920,27 @@ class TreeUnpickler(reader: TastyReader, readByte() readEnd() val expr = readTerm() - def readSelectors(): List[untpd.Tree] = nextByte match { - case IMPORTED => - val start = currentAddr - readByte() - val from = setPos(start, untpd.Ident(readName())) - nextByte match { - case RENAMED => - val start2 = currentAddr - readByte() - val to = setPos(start2, untpd.Ident(readName())) - untpd.Thicket(from, to) :: readSelectors() - case _ => - from :: readSelectors() - } - case _ => - Nil - } setPos(start, Import(expr, readSelectors())) } + def readSelectors()(implicit ctx: Context): List[untpd.Tree] = nextByte match { + case IMPORTED => + val start = currentAddr + readByte() + val from = setPos(start, untpd.Ident(readName())) + nextByte match { + case RENAMED => + val start2 = currentAddr + readByte() + val to = setPos(start2, untpd.Ident(readName())) + untpd.Thicket(from, to) :: readSelectors() + case _ => + from :: readSelectors() + } + case _ => + Nil + } + def readIndexedStats(exprOwner: Symbol, end: Addr)(implicit ctx: Context): List[Tree] = until(end)(readIndexedStat(exprOwner)) @@ -1025,6 +1022,8 @@ class TreeUnpickler(reader: TastyReader, ByNameTypeTree(readTpt()) case NAMEDARG => NamedArg(readName(), readTerm()) + case EMPTYTREE => + EmptyTree case _ => readPathTerm() } @@ -1131,6 +1130,8 @@ class TreeUnpickler(reader: TastyReader, TypeBoundsTree(lo, hi) case HOLE => readHole(end, isType = false) + case UNTYPEDSPLICE => + tpd.UntypedSplice(readUntyped()).withType(readType()) case _ => readPathTerm() } @@ -1200,6 +1201,201 @@ class TreeUnpickler(reader: TastyReader, PickledQuotes.quotedExprToTree(quotedExpr) } } +// ------ Reading untyped trees -------------------------------------------- + + def readUntyped()(implicit ctx: Context): untpd.Tree = { + val start = currentAddr + val tag = readByte() + pickling.println(s"reading term ${astTagToString(tag)} at $start") + + def readDummyType(): Unit = + assert(readByte() == EMPTYTYPETREE) + + def readIdent(): untpd.Ident = readUntyped().asInstanceOf[untpd.Ident] + + def readParams[T <: untpd.MemberDef](tag: Int): List[T] = + collectWhile(nextByte == tag) { + import untpd.modsDeco + val m: T = readUntyped().asInstanceOf[T] + m.withMods(m.mods | Param).asInstanceOf[T] + } + + def readParamss(): List[List[untpd.ValDef]] = + collectWhile(nextByte == PARAMS) { + readByte() + readEnd() + readParams[untpd.ValDef](PARAM) + } + + def readCases(end: Addr): List[untpd.CaseDef] = + collectWhile((nextUnsharedTag == CASEDEF) && currentAddr != end) { + readUntyped().asInstanceOf[untpd.CaseDef] + } + + def readSimpleTerm(): untpd.Tree = (tag: @switch) match { + case TERMREF => + val name = readName() + readDummyType() + untpd.Ident(name) + case TYPEREF => + val name = readName().toTypeName + readDummyType() + untpd.Ident(name) + case SELECT => + val name = readName() + val qual = readUntyped() + untpd.Select(qual, name) + case SELECTtpt => + val name = readName().toTypeName + val qual = readUntyped() + untpd.Select(qual, name) + case QUALTHIS => + untpd.This(readIdent()) + case NEW => + untpd.New(readUntyped()) + case THROW => + untpd.Throw(readUntyped()) + case SINGLETONtpt => + untpd.SingletonTypeTree(readUntyped()) + case BYNAMEtpt => + untpd.ByNameTypeTree(readUntyped()) + case NAMEDARG => + untpd.NamedArg(readName(), readUntyped()) + case EMPTYTREE => + untpd.EmptyTree + case EMPTYTYPETREE => + untpd.TypeTree() + case _ => + untpd.Literal(readConstant(tag)) + } + + def readLengthTerm(): untpd.Tree = { + val end = readEnd() + + def readMods(): untpd.Modifiers = { + val (flags, annots, privateWithin) = + readModifiers(end, readUntypedAnnot, readUntypedWithin, EmptyTypeName) + untpd.Modifiers(flags, privateWithin, annots.map(_(NoSymbol))) + } + + def readRhs(): untpd.Tree = + if (nothingButMods(end)) untpd.EmptyTree else readUntyped() + + val result = (tag: @switch) match { + case SUPER => + val qual = readUntyped() + val mixId = ifBefore(end)(readIdent(), untpd.EmptyTypeIdent) + untpd.Super(qual, mixId) + case APPLY => + val fn = readUntyped() + untpd.Apply(fn, until(end)(readUntyped())) + case TYPEAPPLY => + untpd.TypeApply(readUntyped(), until(end)(readUntyped())) + case TYPED => + val expr = readUntyped() + val tpt = readUntyped() + untpd.Typed(expr, tpt) + case ASSIGN => + untpd.Assign(readUntyped(), readUntyped()) + case BLOCK => + val expr = readUntyped() + val stats = until(end)(readUntyped()) + untpd.Block(stats, expr) + case IF => + untpd.If(readUntyped(), readUntyped(), readUntyped()) + case MATCH => + untpd.Match(readUntyped(), readCases(end)) + case CASEDEF => + val pat = readUntyped() + val rhs = readUntyped() + val guard = ifBefore(end)(readUntyped(), untpd.EmptyTree) + untpd.CaseDef(pat, guard, rhs) + case RETURN => + readNat() + val expr = ifBefore(end)(readUntyped(), untpd.EmptyTree) + untpd.Return(expr, untpd.EmptyTree) + case TRY => + untpd.Try(readUntyped(), readCases(end), ifBefore(end)(readUntyped(), untpd.EmptyTree)) + case BIND => + val name = readName() + readDummyType() + untpd.Bind(name, readUntyped()) + case ALTERNATIVE => + untpd.Alternative(until(end)(readUntyped())) + case DEFDEF => + untpd.DefDef(readName(), readParams[TypeDef](TYPEPARAM), readParamss(), readUntyped(), readRhs()) + .withMods(readMods()) + case VALDEF | PARAM => + untpd.ValDef(readName(), readUntyped(), readRhs()) + .withMods(readMods()) + case TYPEDEF | TYPEPARAM => + untpd.TypeDef(readName().toTypeName, readUntyped()) + .withMods(readMods()) + case OBJECTDEF => + untpd.ModuleDef(readName(), readUntyped().asInstanceOf[untpd.Template]) + .withMods(readMods()) + case TEMPLATE => + val parents = collectWhile(nextByte != SELFDEF && nextByte != DEFDEF)(readUntyped()) + val self = + if (nextByte == SELFDEF) { + readByte() + untpd.ValDef(readName(), readUntyped(), untpd.EmptyTree) + } + else untpd.EmptyValDef + val constr = readUntyped().asInstanceOf[untpd.DefDef] + val body = until(end)(readUntyped()) + untpd.Template(constr, parents, self, body) + case IMPORT => + untpd.Import(readUntyped(), readSelectors()) + case REFINEDtpt => + untpd.RefinedTypeTree(readUntyped(), until(end)(readUntyped())) + case APPLIEDtpt => + untpd.AppliedTypeTree(readUntyped(), until(end)(readUntyped())) + case ANDtpt => + untpd.AndTypeTree(readUntyped(), readUntyped()) + case ORtpt => + untpd.OrTypeTree(readUntyped(), readUntyped()) + case ANNOTATEDtpt => + untpd.Annotated(readUntyped(), readUntyped()) + case LAMBDAtpt => + val tparams = readParams[TypeDef](TYPEPARAM) + val body = readUntyped() + untpd.LambdaTypeTree(tparams, body) + case TYPEBOUNDStpt => + val lo = readUntyped() + val hi = ifBefore(end)(readUntyped(), lo) + untpd.TypeBoundsTree(lo, hi) + case TYPEDSPLICE => + untpd.TypedSplice(readTerm()) + case FUNCTION => + val body = readUntyped() + import untpd.modsDeco + val params = until(end)(readUntyped()).map { + case param: untpd.ValDef => param.withMods(param.mods | Param) + case param => param + } + untpd.Function(params, body) + case INFIXOP => + untpd.InfixOp(readUntyped(), readIdent(), readUntyped()) + case PATDEF => + val tpt = readUntyped() + val rhs = readUntyped() + val pats = collectWhile(!nothingButMods(end))(readUntyped()) + untpd.PatDef(readMods(), pats, tpt, rhs) + } + assert(currentAddr == end, s"$start $currentAddr $end ${astTagToString(tag)}") + result + } + + val tree = if (tag < firstLengthTreeTag) readSimpleTerm() else readLengthTerm() + setPos(start, tree) + } + + private val readUntypedWithin: Context => TypeName = + implicit ctx => readName().toTypeName + + private val readUntypedAnnot: Context => Symbol => untpd.Tree = + implicit ctx => _ => readUntyped() // ------ Setting positions ------------------------------------------------ diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/PickleBuffer.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/PickleBuffer.scala index 92baaab565e6..31f05cf71f4e 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/PickleBuffer.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/PickleBuffer.scala @@ -232,7 +232,7 @@ object PickleBuffer { EXPANDEDNAME -> Scala2ExpandedName, IMPLCLASS -> (Scala2PreSuper, ImplClass), SPECIALIZED -> Specialized, - VBRIDGE -> VBridge, + VBRIDGE -> EmptyFlags, VARARGS -> JavaVarargs, ENUM -> Enum) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index f631104d80aa..a340d9160150 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -801,11 +801,14 @@ object Parsers { case FORSOME => syntaxError(ExistentialTypesNoLongerSupported()); t case _ => if (imods.is(Implicit) && !t.isInstanceOf[FunctionWithMods]) - syntaxError("Types with implicit keyword can only be function types", Position(start, start + nme.IMPLICITkw.asSimpleName.length)) + syntaxError("Types with implicit keyword can only be function types", implicitKwPos(start)) t } } + private def implicitKwPos(start: Int): Position = + Position(start, start + nme.IMPLICITkw.asSimpleName.length) + /** TypedFunParam ::= id ':' Type */ def typedFunParam(start: Offset, name: TermName, mods: Modifiers = EmptyModifiers): Tree = atPos(start) { accept(COLON) @@ -1092,6 +1095,7 @@ object Parsers { * | SimpleExpr1 ArgumentExprs `=' Expr * | PostfixExpr [Ascription] * | PostfixExpr `match' `{' CaseClauses `}' + * | `implicit' `match' `{' ImplicitCaseClauses `}' * Bindings ::= `(' [Binding {`,' Binding}] `)' * Binding ::= (id | `_') [`:' Type] * Ascription ::= `:' CompoundType @@ -1106,7 +1110,8 @@ object Parsers { val start = in.offset if (in.token == IMPLICIT || in.token == ERASED) { val imods = modifiers(funArgMods) - implicitClosure(start, location, imods) + if (in.token == MATCH) implicitMatch(start, imods) + else implicitClosure(start, location, imods) } else { val saved = placeholderParams placeholderParams = Nil @@ -1207,9 +1212,7 @@ object Parsers { case COLON => ascription(t, location) case MATCH => - atPos(startOffset(t), in.skipToken()) { - inBraces(Match(t, caseClauses())) - } + matchExpr(t) case _ => t } @@ -1240,6 +1243,37 @@ object Parsers { } } + /** `match' { CaseClauses } + * `match' { ImplicitCaseClauses } + */ + def matchExpr(t: Tree) = + atPos(startOffset(t), in.skipToken()) { + inBraces(Match(t, caseClauses())) + } + + /** `match' { ImplicitCaseClauses } + */ + def implicitMatch(start: Int, imods: Modifiers) = { + def markFirstIllegal(mods: List[Mod]) = mods match { + case mod :: _ => syntaxError(em"illegal modifier for implicit match", mod.pos) + case _ => + } + imods.mods match { + case Mod.Implicit() :: mods => markFirstIllegal(mods) + case mods => markFirstIllegal(mods) + } + val result @ Match(t, cases) = matchExpr(ImplicitScrutinee().withPos(implicitKwPos(start))) + for (CaseDef(pat, _, _) <- cases) { + def isImplicitPattern(pat: Tree) = pat match { + case Typed(pat1, _) => isVarPattern(pat1) + case pat => isVarPattern(pat) + } + if (!isImplicitPattern(pat)) + syntaxError(em"not a legal pattern for an implicit match", pat.pos) + } + result + } + /** FunParams ::= Bindings * | id * | `_' @@ -1544,8 +1578,9 @@ object Parsers { } } - /** CaseClauses ::= CaseClause {CaseClause} - */ + /** CaseClauses ::= CaseClause {CaseClause} + * ImplicitCaseClauses ::= ImplicitCaseClause {ImplicitCaseClause} + */ def caseClauses(): List[CaseDef] = { val buf = new ListBuffer[CaseDef] buf += caseClause() @@ -1553,7 +1588,8 @@ object Parsers { buf.toList } - /** CaseClause ::= case Pattern [Guard] `=>' Block + /** CaseClause ::= case Pattern [Guard] `=>' Block + * ImplicitCaseClause ::= case PatVar [Ascription] [Guard] `=>' Block */ def caseClause(): CaseDef = atPos(in.offset) { accept(CASE) @@ -1679,7 +1715,6 @@ object Parsers { case FINAL => Mod.Final() case IMPLICIT => Mod.Implicit() case ERASED => Mod.Erased() - case INLINE => Mod.Inline() case TRANSPARENT => Mod.Transparent() case LAZY => Mod.Lazy() case OVERRIDE => Mod.Override() @@ -1791,7 +1826,6 @@ object Parsers { */ def annot() = adjustStart(accept(AT)) { - if (in.token == INLINE) in.token = BACKQUOTED_IDENT // allow for now ensureApplied(parArgumentExprss(wrapNew(simpleType()))) } @@ -1888,7 +1922,7 @@ object Parsers { addMod(mods, mod) } else { - if (!(mods.flags &~ (ParamAccessor | Inline)).isEmpty) + if (!(mods.flags &~ (ParamAccessor | Transparent)).isEmpty) syntaxError("`val' or `var' expected") if (firstClauseOfCaseClass) mods else mods | PrivateLocal @@ -1896,7 +1930,7 @@ object Parsers { } } else { - if (in.token == INLINE) mods = addModifier(mods) + if (in.token == TRANSPARENT) mods = addModifier(mods) mods = atPos(start) { mods | Param } } atPos(start, nameStart) { @@ -2534,8 +2568,12 @@ object Parsers { if (in.token == IMPLICIT || in.token == ERASED) { val start = in.offset var imods = modifiers(funArgMods) - if (isBindingIntro) stats += implicitClosure(start, Location.InBlock, imods) - else stats +++= localDef(start, imods) + if (isBindingIntro) + stats += implicitClosure(start, Location.InBlock, imods) + else if (in.token == MATCH) + stats += implicitMatch(start, imods) + else + stats +++= localDef(start, imods) } else { stats +++= localDef(in.offset) } diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 68443e5b2125..7aa2dc4d36eb 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -198,7 +198,9 @@ object Scanners { private def handleMigration(keyword: Token): Token = if (!isScala2Mode) keyword - else if (keyword == INLINE) treatAsIdent() + else if ( keyword == ENUM + || keyword == ERASED + || keyword == TRANSPARENT) treatAsIdent() else keyword diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 17b3e055f418..2c2ff229544e 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -91,8 +91,9 @@ abstract class TokensCommon { //final val LAZY = 59; enter(LAZY, "lazy") //final val THEN = 60; enter(THEN, "then") //final val FORSOME = 61; enter(FORSOME, "forSome") // TODO: deprecate - //final val INLINE = 62; enter(INLINE, "inline") - //final val ENUM = 63; enter(ENUM, "enum") + //final val TRANSPARENT = 62; enter(TRANSPARENT, "transparent") + //final val ENUM = 63; enter(ENUM, "enum") + //final val ERASED = 64; enter(ERASED, "erased") /** special symbols */ final val COMMA = 70; enter(COMMA, "','") @@ -175,10 +176,9 @@ object Tokens extends TokensCommon { final val LAZY = 59; enter(LAZY, "lazy") final val THEN = 60; enter(THEN, "then") final val FORSOME = 61; enter(FORSOME, "forSome") // TODO: deprecate - final val INLINE = 62; enter(INLINE, "inline") - final val TRANSPARENT = 63; enter(TRANSPARENT, "transparent") - final val ENUM = 64; enter(ENUM, "enum") - final val ERASED = 65; enter(ERASED, "erased") + final val TRANSPARENT = 62; enter(TRANSPARENT, "transparent") + final val ENUM = 63; enter(ENUM, "enum") + final val ERASED = 64; enter(ERASED, "erased") /** special symbols */ final val NEWLINE = 78; enter(NEWLINE, "end of statement", "new line") @@ -227,7 +227,7 @@ object Tokens extends TokensCommon { final val defIntroTokens = templateIntroTokens | dclIntroTokens final val localModifierTokens = BitSet( - ABSTRACT, FINAL, SEALED, IMPLICIT, INLINE, TRANSPARENT, LAZY, ERASED) + ABSTRACT, FINAL, SEALED, IMPLICIT, TRANSPARENT, LAZY, ERASED) final val accessModifierTokens = BitSet( PRIVATE, PROTECTED) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index b64ec3a5ad92..9990d1d9ea8f 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -365,7 +365,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case SeqLiteral(elems, elemtpt) => "[" ~ toTextGlobal(elems, ",") ~ " : " ~ toText(elemtpt) ~ "]" case tree @ Inlined(call, bindings, body) => - (("/* inlined from " ~ toText(call) ~ "*/ ") provided !homogenizedView && !ctx.settings.YshowNoInline.value) ~ + (("/* inlined from " ~ toText(call) ~ " */ ") `provided` + !call.isEmpty && !homogenizedView && !ctx.settings.YshowNoInline.value) ~ blockText(bindings :+ body) case tpt: untpd.DerivedTypeTree => "" @@ -446,6 +447,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case TypedSplice(t) => if (ctx.settings.YprintDebug.value) "[" ~ toText(t) ~ "]#TS#" else toText(t) + case tpd.UntypedSplice(t) => + if (ctx.settings.YprintDebug.value) "[" ~ toText(t) ~ ":" ~ toText(tree.typeOpt) ~ "]#US#" + else toText(t) case tree @ ModuleDef(name, impl) => withEnclosingDef(tree) { modText(tree.mods, NoSymbol, keywordStr("object")) ~~ nameIdText(tree) ~ toTextTemplate(impl) diff --git a/compiler/src/dotty/tools/dotc/quoted/Toolbox.scala b/compiler/src/dotty/tools/dotc/quoted/Toolbox.scala index 72d35f6159d8..827157f5f3e2 100644 --- a/compiler/src/dotty/tools/dotc/quoted/Toolbox.scala +++ b/compiler/src/dotty/tools/dotc/quoted/Toolbox.scala @@ -17,7 +17,7 @@ object Toolbox { case expr: LiftedExpr[T] => expr.value case expr: TastyTreeExpr[Tree] @unchecked => - throw new Exception("Cannot call `Expr.run` on an `Expr` that comes from an inline macro argument.") + throw new Exception("Cannot call `Expr.run` on an `Expr` that comes from a transparent macro argument.") case _ => synchronized(driver.run(expr, settings)) } diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java index 87136eda5ebe..8233e3305d2a 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java @@ -90,7 +90,7 @@ public enum ErrorMessageID { OnlyCaseClassOrCaseObjectAllowedID, ExpectedClassOrObjectDefID, AnonymousFunctionMissingParamTypeID, - SuperCallsNotAllowedInlineID, + SuperCallsNotAllowedTransparentID, ModifiersNotAllowedID, WildcardOnTypeArgumentNotAllowedOnNewID, FunctionTypeNeedsNonEmptyParameterListID, @@ -98,7 +98,7 @@ public enum ErrorMessageID { DuplicatePrivateProtectedQualifierID, ExpectedStartOfTopLevelDefinitionID, MissingReturnTypeWithReturnStatementID, - NoReturnFromInlineID, + NoReturnFromTransparentID, ReturnOutsideMethodDefinitionID, UncheckedTypePatternID, ExtendFinalClassID, @@ -125,7 +125,7 @@ public enum ErrorMessageID { UnableToEmitSwitchID, MissingCompanionForStaticID, PolymorphicMethodMissingTypeInParentID, - ParamsNoInlineID, + ParamsNoTransparentID, JavaSymbolIsNotAValueID, DoubleDeclarationID, MatchCaseOnlyNullWarningID, diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 818e38ffb4b7..ef0e416ae1a2 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -1692,10 +1692,10 @@ object messages { val explanation = "" } - case class SuperCallsNotAllowedInline(symbol: Symbol)(implicit ctx: Context) - extends Message(SuperCallsNotAllowedInlineID) { + case class SuperCallsNotAllowedTransparent(symbol: Symbol)(implicit ctx: Context) + extends Message(SuperCallsNotAllowedTransparentID) { val kind = "Syntax" - val msg = s"super call not allowed in inline $symbol" + val msg = s"super call not allowed in transparent $symbol" val explanation = "Method inlining prohibits calling superclass methods, as it may lead to confusion about which super is being called." } @@ -1743,12 +1743,12 @@ object messages { hl"you have to provide either ${"class"}, ${"trait"}, ${"object"}, or ${"enum"} definitions after qualifiers" } - case class NoReturnFromInline(owner: Symbol)(implicit ctx: Context) - extends Message(NoReturnFromInlineID) { + case class NoReturnFromTransparent(owner: Symbol)(implicit ctx: Context) + extends Message(NoReturnFromTransparentID) { val kind = "Syntax" - val msg = hl"no explicit ${"return"} allowed from inline $owner" + val msg = hl"no explicit ${"return"} allowed from transparent $owner" val explanation = - hl"""Methods marked with ${"inline"} modifier may not use ${"return"} statements. + hl"""Methods marked with ${"transparent"} modifier may not use ${"return"} statements. |Instead, you should rely on the last expression's value being |returned from a method. |""" @@ -2054,10 +2054,10 @@ object messages { |polymorphic methods.""" } - case class ParamsNoInline(owner: Symbol)(implicit ctx: Context) - extends Message(ParamsNoInlineID) { + case class ParamsNoTransparent(owner: Symbol)(implicit ctx: Context) + extends Message(ParamsNoTransparentID) { val kind = "Syntax" - val msg = hl"""${"inline"} modifier cannot be used for a ${owner.showKind} parameter""" + val msg = hl"""${"transparent"} modifier cannot be used for a parameter of a non-transparent method""" val explanation = "" } diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 4dcd282852c3..4418a5f4f518 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -590,7 +590,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder val annots = new mutable.ListBuffer[api.Annotation] if (Inliner.hasBodyToInline(s)) { - // FIXME: If the body of an inline method changes, all the reverse + // FIXME: If the body of a transparent method changes, all the reverse // dependencies of this method need to be recompiled. sbt has no way // of tracking method bodies, so as a hack we include the pretty-printed // typed tree of the method as part of the signature we send to sbt. diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala index 593801633f76..3e8698589771 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala @@ -358,7 +358,7 @@ private class ExtractDependenciesCollector extends tpd.TreeTraverser { thisTreeT } tree match { - case Inlined(call, _, _) => + case Inlined(call, _, _) if !call.isEmpty => // The inlined call is normally ignored by TreeTraverser but we need to // record it as a dependency traverse(call) diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/FlagSet.scala b/compiler/src/dotty/tools/dotc/tastyreflect/FlagSet.scala index 27f56edb6e2c..3c553e5de0e8 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/FlagSet.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/FlagSet.scala @@ -14,7 +14,7 @@ class FlagSet(flags: Flags.FlagSet) extends scala.tasty.FlagSet { def isErased: Boolean = flags.is(Erased) def isLazy: Boolean = flags.is(Lazy) def isOverride: Boolean = flags.is(Override) - def isInline: Boolean = flags.is(Inline) + def isTransparent: Boolean = flags.is(Transparent) def isMacro: Boolean = flags.is(Macro) def isStatic: Boolean = flags.is(JavaStatic) def isObject: Boolean = flags.is(Module) @@ -45,7 +45,7 @@ class FlagSet(flags: Flags.FlagSet) extends scala.tasty.FlagSet { if (isErased) flags += "erased" if (isLazy) flags += "lazy" if (isOverride) flags += "override" - if (isInline) flags += "inline" + if (isTransparent) flags += "transparent" if (isMacro) flags += "macro" if (isStatic) flags += "javaStatic" if (isObject) flags += "object" diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/FromSymbol.scala b/compiler/src/dotty/tools/dotc/tastyreflect/FromSymbol.scala index 150149b38e0f..c4fcfce1305a 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/FromSymbol.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/FromSymbol.scala @@ -23,9 +23,9 @@ object FromSymbol { val constrSym = cls.unforcedDecls.find(_.isPrimaryConstructor) if (!constrSym.exists) return tpd.EmptyTree val constr = tpd.DefDef(constrSym.asTerm) + val parents = cls.classParents.map(tpd.TypeTree(_)) val body = cls.unforcedDecls.filter(!_.isPrimaryConstructor).map(s => definition(s)) - val superArgs = Nil // TODO - tpd.ClassDef(cls, constr, body, superArgs) + tpd.ClassDefWithParents(cls, constr, parents, body) } def typeDef(sym: TypeSymbol)(implicit ctx: Context): tpd.TypeDef = tpd.TypeDef(sym) diff --git a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala index fa3c9efbfa9a..bffd84779544 100644 --- a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala +++ b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala @@ -145,7 +145,7 @@ abstract class AccessProxies { def accessorIfNeeded(tree: Tree)(implicit ctx: Context): Tree = tree match { case tree: RefTree if needsAccessor(tree.symbol) => if (tree.symbol.isConstructor) { - ctx.error("Implementation restriction: cannot use private constructors in inline methods", tree.pos) + ctx.error("Implementation restriction: cannot use private constructors in transparent methods", tree.pos) tree // TODO: create a proper accessor for the private constructor } else useAccessor(tree) @@ -162,7 +162,7 @@ object AccessProxies { def recur(cls: Symbol): Symbol = if (!cls.exists) NoSymbol else if (cls.derivesFrom(accessed.owner) || - cls.companionModule.moduleClass == accessed.owner) cls + cls.companionModule.moduleClass == accessed.owner) cls else recur(cls.owner) recur(ctx.owner) } diff --git a/compiler/src/dotty/tools/dotc/transform/DropInlined.scala b/compiler/src/dotty/tools/dotc/transform/DropInlined.scala deleted file mode 100644 index 55dec8628a48..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/DropInlined.scala +++ /dev/null @@ -1,15 +0,0 @@ -package dotty.tools.dotc -package transform - -import typer.Inliner -import core.Contexts.Context -import MegaPhase.MiniPhase - -/** Drop Inlined nodes */ -class DropInlined extends MiniPhase { - import ast.tpd._ - override def phaseName = "dropInlined" - - override def transformInlined(tree: Inlined)(implicit ctx: Context): Tree = - Inliner.dropInlined(tree) -} diff --git a/compiler/src/dotty/tools/dotc/transform/ErasedDecls.scala b/compiler/src/dotty/tools/dotc/transform/ErasedDecls.scala deleted file mode 100644 index c5756dcf3d8f..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/ErasedDecls.scala +++ /dev/null @@ -1,50 +0,0 @@ -package dotty.tools.dotc.transform - -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.core.Contexts._ -import dotty.tools.dotc.core.DenotTransformers.InfoTransformer -import dotty.tools.dotc.core.Flags._ -import dotty.tools.dotc.core.Symbols._ -import dotty.tools.dotc.core.Phases.Phase -import dotty.tools.dotc.core.Types._ -import dotty.tools.dotc.transform.MegaPhase.MiniPhase - -/** This phase removes erased declarations of val`s (except for parameters). - * - * `erased val x = ...` are removed - */ -class ErasedDecls extends MiniPhase with InfoTransformer { - import tpd._ - - override def phaseName: String = "erasedDecls" - - override def runsAfterGroupsOf = Set( - PatternMatcher.name // Make sure pattern match errors are emitted - ) - - /** Check what the phase achieves, to be called at any point after it is finished. */ - override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = tree match { - case tree: ValOrDefDef if !tree.symbol.is(Param) => assert(!tree.symbol.is(Erased, butNot = Param)) - case _ => - } - - - /* Tree transform */ - - override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = transformValOrDefDef(tree) - override def transformValDef(tree: ValDef)(implicit ctx: Context): Tree = transformValOrDefDef(tree) - - private def transformValOrDefDef(tree: ValOrDefDef)(implicit ctx: Context): Tree = - if (tree.symbol.is(Erased, butNot = Param)) EmptyTree else tree - - - /* Info transform */ - - override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp match { - case tp: ClassInfo => tp.derivedClassInfo(decls = tp.decls.filteredScope(!_.is(Erased))) - case _ => tp - } - - override protected def mayChange(sym: Symbol)(implicit ctx: Context): Boolean = - sym.isClass && !sym.is(JavaDefined) && !sym.is(Scala2x) -} diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 811ac9c9ce04..88ac3e9900bc 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -16,6 +16,7 @@ import core.Decorators._ import core.Constants._ import core.Definitions._ import typer.NoChecking +import typer.Inliner import typer.ProtoTypes._ import typer.ErrorReporting._ import core.TypeErasure._ @@ -37,7 +38,7 @@ class Erasure extends Phase with DenotTransformer { /** List of names of phases that should precede this phase */ override def runsAfter = Set(InterceptedMethods.name, Splitter.name, ElimRepeated.name) - override def changesMembers: Boolean = true // the phase adds bridges + override def changesMembers: Boolean = true // the phase adds bridges override def changesParents: Boolean = true // the phase drops Any def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref match { @@ -317,6 +318,17 @@ object Erasure { class Typer(erasurePhase: DenotTransformer) extends typer.ReTyper with NoChecking { import Boxing._ + private def checkNotErased(tree: Tree)(implicit ctx: Context): tree.type = { + if (tree.symbol.is(Flags.Erased) && !ctx.mode.is(Mode.Type)) + ctx.error(em"${tree.symbol} is declared as erased, but is in fact used", tree.pos) + tree + } + + def erasedDef(sym: Symbol)(implicit ctx: Context) = { + if (sym.owner.isClass) sym.dropAfter(erasurePhase) + tpd.EmptyTree + } + def erasedType(tree: untpd.Tree)(implicit ctx: Context): Type = { val tp = tree.typeOpt if (tree.isTerm) erasedRef(tp) else valueErasure(tp) @@ -356,6 +368,10 @@ object Erasure { else super.typedLiteral(tree) + override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context): Tree = { + checkNotErased(super.typedIdent(tree, pt)) + } + /** Type check select nodes, applying the following rewritings exhaustively * on selections `e.m`, where `OT` is the type of the owner of `m` and `ET` * is the erased type of the selection's original qualifier expression. @@ -436,7 +452,7 @@ object Erasure { } } - recur(typed(tree.qualifier, AnySelectionProto)) + checkNotErased(recur(typed(tree.qualifier, AnySelectionProto))) } override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree = @@ -532,43 +548,52 @@ object Erasure { } } - override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(implicit ctx: Context): ValDef = - super.typedValDef(untpd.cpy.ValDef(vdef)( - tpt = untpd.TypedSplice(TypeTree(sym.info).withPos(vdef.tpt.pos))), sym) + override def typedInlined(tree: untpd.Inlined, pt: Type)(implicit ctx: Context): Tree = + super.typedInlined(tree, pt) match { + case tree: Inlined => Inliner.dropInlined(tree) + } + + override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(implicit ctx: Context): Tree = + if (sym.is(Flags.Erased)) erasedDef(sym) + else + super.typedValDef(untpd.cpy.ValDef(vdef)( + tpt = untpd.TypedSplice(TypeTree(sym.info).withPos(vdef.tpt.pos))), sym) /** Besides normal typing, this function also compacts anonymous functions * with more than `MaxImplementedFunctionArity` parameters to ise a single * parameter of type `[]Object`. */ - override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context) = { - val restpe = - if (sym.isConstructor) defn.UnitType - else sym.info.resultType - var vparamss1 = (outer.paramDefs(sym) ::: ddef.vparamss.flatten) :: Nil - var rhs1 = ddef.rhs match { - case id @ Ident(nme.WILDCARD) => untpd.TypedSplice(id.withType(restpe)) - case _ => ddef.rhs - } - if (sym.isAnonymousFunction && vparamss1.head.length > MaxImplementedFunctionArity) { - val bunchedParam = ctx.newSymbol(sym, nme.ALLARGS, Flags.TermParam, JavaArrayType(defn.ObjectType)) - def selector(n: Int) = ref(bunchedParam) - .select(defn.Array_apply) - .appliedTo(Literal(Constant(n))) - val paramDefs = vparamss1.head.zipWithIndex.map { - case (paramDef, idx) => - assignType(untpd.cpy.ValDef(paramDef)(rhs = selector(idx)), paramDef.symbol) + override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context): Tree = + if (sym.is(Flags.Erased)) erasedDef(sym) + else { + val restpe = + if (sym.isConstructor) defn.UnitType + else sym.info.resultType + var vparamss1 = (outer.paramDefs(sym) ::: ddef.vparamss.flatten) :: Nil + var rhs1 = ddef.rhs match { + case id @ Ident(nme.WILDCARD) => untpd.TypedSplice(id.withType(restpe)) + case _ => ddef.rhs } - vparamss1 = (tpd.ValDef(bunchedParam) :: Nil) :: Nil - rhs1 = untpd.Block(paramDefs, rhs1) + if (sym.isAnonymousFunction && vparamss1.head.length > MaxImplementedFunctionArity) { + val bunchedParam = ctx.newSymbol(sym, nme.ALLARGS, Flags.TermParam, JavaArrayType(defn.ObjectType)) + def selector(n: Int) = ref(bunchedParam) + .select(defn.Array_apply) + .appliedTo(Literal(Constant(n))) + val paramDefs = vparamss1.head.zipWithIndex.map { + case (paramDef, idx) => + assignType(untpd.cpy.ValDef(paramDef)(rhs = selector(idx)), paramDef.symbol) + } + vparamss1 = (tpd.ValDef(bunchedParam) :: Nil) :: Nil + rhs1 = untpd.Block(paramDefs, rhs1) + } + vparamss1 = vparamss1.mapConserve(_.filterConserve(!_.symbol.is(Flags.Erased))) + val ddef1 = untpd.cpy.DefDef(ddef)( + tparams = Nil, + vparamss = vparamss1, + tpt = untpd.TypedSplice(TypeTree(restpe).withPos(ddef.tpt.pos)), + rhs = rhs1) + super.typedDefDef(ddef1, sym) } - vparamss1 = vparamss1.mapConserve(_.filterConserve(!_.symbol.is(Flags.Erased))) - val ddef1 = untpd.cpy.DefDef(ddef)( - tparams = Nil, - vparamss = vparamss1, - tpt = untpd.TypedSplice(TypeTree(restpe).withPos(ddef.tpt.pos)), - rhs = rhs1) - super.typedDefDef(ddef1, sym) - } override def typedClosure(tree: untpd.Closure, pt: Type)(implicit ctx: Context) = { val xxl = defn.isXXLFunctionClass(tree.typeOpt.typeSymbol) diff --git a/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala index 02d072bf6a86..198118e43f03 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -21,7 +21,7 @@ import Decorators._ import SymUtils._ /** - * Perform Step 1 in the inline classes SIP: Creates extension methods for all + * Perform Step 1 in the transparent classes SIP: Creates extension methods for all * methods in a value class, except parameter or super accessors, or constructors. * * Additionally, for a value class V, let U be the underlying type after erasure. We add diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 92230e38310b..cd3bec37f0da 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -30,7 +30,7 @@ object FirstTransform { /** The first tree transform * - eliminates some kinds of trees: Imports, NamedArgs - * - stubs out native methods + * - stubs out native and typelevel methods * - eliminates self tree in Template and self symbol in ClassInfo * - collapses all type trees to trees of class TypeTree * - converts idempotent expressions with constant types @@ -109,12 +109,15 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => } override def transformDefDef(ddef: DefDef)(implicit ctx: Context) = { - if (ddef.symbol.hasAnnotation(defn.NativeAnnot)) { - ddef.symbol.resetFlag(Deferred) - DefDef(ddef.symbol.asTerm, - _ => ref(defn.Sys_errorR).withPos(ddef.pos) - .appliedTo(Literal(Constant("native method stub")))) - } else ddef + val meth = ddef.symbol.asTerm + if (meth.hasAnnotation(defn.NativeAnnot)) { + meth.resetFlag(Deferred) + polyDefDef(meth, + _ => _ => ref(defn.Sys_errorR).withPos(ddef.pos) + .appliedTo(Literal(Constant(s"native method stub")))) + + } + else ddef } override def transformStats(trees: List[Tree])(implicit ctx: Context): List[Tree] = diff --git a/compiler/src/dotty/tools/dotc/transform/IsInstanceOfChecker.scala b/compiler/src/dotty/tools/dotc/transform/IsInstanceOfChecker.scala deleted file mode 100644 index f2d10f3fc677..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/IsInstanceOfChecker.scala +++ /dev/null @@ -1,151 +0,0 @@ -package dotty.tools.dotc -package transform - -import util.Positions._ -import MegaPhase.MiniPhase -import core._ -import Contexts.Context, Types._, Decorators._, Symbols._, typer._, ast._, NameKinds._ -import TypeUtils._, Flags._ -import config.Printers.{ transforms => debug } - -/** Check runtime realizability of type test, see the documentation for `Checkable`. - */ -class IsInstanceOfChecker extends MiniPhase { - - import ast.tpd._ - - val phaseName = "isInstanceOfChecker" - - override def transformTypeApply(tree: TypeApply)(implicit ctx: Context): Tree = { - def ensureCheckable(qual: Tree, pt: Tree): Tree = { - if (!Checkable.checkable(qual.tpe, pt.tpe, tree.pos)) - ctx.warning( - s"the type test for ${pt.show} cannot be checked at runtime", - tree.pos - ) - - tree - } - - tree.fun match { - case fn: Select - if fn.symbol == defn.Any_typeTest || fn.symbol == defn.Any_isInstanceOf => - ensureCheckable(fn.qualifier, tree.args.head) - case _ => tree - } - } -} - -object Checkable { - import Inferencing._ - import ProtoTypes._ - - /** Whether `(x:X).isInstanceOf[P]` can be checked at runtime? - * - * First do the following substitution: - * (a) replace `T @unchecked` and pattern binder types (e.g., `_$1`) in P with WildcardType - * (b) replace pattern binder types (e.g., `_$1`) in X: - * - variance = 1 : hiBound - * - variance = -1 : loBound - * - variance = 0 : OrType(Any, Nothing) // TODO: use original type param bounds - * - * Then check: - * - * 1. if `X <:< P`, TRUE - * 2. if `P` is a singleton type, TRUE - * 3. if `P` refers to an abstract type member or type parameter, FALSE - * 4. if `P = Array[T]`, checkable(E, T) where `E` is the element type of `X`, defaults to `Any`. - * 5. if `P` is `pre.F[Ts]` and `pre.F` refers to a class which is not `Array`: - * (a) replace `Ts` with fresh type variables `Xs` - * (b) constrain `Xs` with `pre.F[Xs] <:< X` - * (c) instantiate Xs and check `pre.F[Xs] <:< P` - * 6. if `P = T1 | T2` or `P = T1 & T2`, checkable(X, T1) && checkable(X, T2). - * 7. if `P` is a refinement type, FALSE - * 8. otherwise, TRUE - */ - def checkable(X: Type, P: Type, pos: Position)(implicit ctx: Context): Boolean = { - def isAbstract(P: Type) = !P.dealias.typeSymbol.isClass - def isPatternTypeSymbol(sym: Symbol) = !sym.isClass && sym.is(Case) - - def replaceP(tp: Type)(implicit ctx: Context) = new TypeMap { - def apply(tp: Type) = tp match { - case tref: TypeRef - if isPatternTypeSymbol(tref.typeSymbol) => WildcardType - case AnnotatedType(_, annot) - if annot.symbol == defn.UncheckedAnnot => WildcardType - case _ => mapOver(tp) - } - }.apply(tp) - - def replaceX(tp: Type)(implicit ctx: Context) = new TypeMap { - def apply(tp: Type) = tp match { - case tref: TypeRef - if isPatternTypeSymbol(tref.typeSymbol) => - if (variance == 1) tref.info.hiBound - else if (variance == -1) tref.info.loBound - else OrType(defn.AnyType, defn.NothingType) - case _ => mapOver(tp) - } - }.apply(tp) - - /** Approximate type parameters depending on variance */ - def stripTypeParam(tp: Type)(implicit ctx: Context) = new ApproximatingTypeMap { - def apply(tp: Type): Type = tp match { - case tp: TypeRef if tp.underlying.isInstanceOf[TypeBounds] => - val lo = apply(tp.info.loBound) - val hi = apply(tp.info.hiBound) - range(lo, hi) - case _ => - mapOver(tp) - } - }.apply(tp) - - def isClassDetermined(X: Type, P: AppliedType)(implicit ctx: Context) = { - val AppliedType(tycon, _) = P - val typeLambda = tycon.ensureLambdaSub.asInstanceOf[TypeLambda] - val tvars = constrained(typeLambda, untpd.EmptyTree, alwaysAddTypeVars = true)._2.map(_.tpe) - val P1 = tycon.appliedTo(tvars) - - debug.println("P : " + P) - debug.println("P1 : " + P1) - debug.println("X : " + X) - - P1 <:< X // constraint P1 - - // use fromScala2x to avoid generating pattern bound symbols - maximizeType(P1, pos, fromScala2x = true) - - val res = P1 <:< P - debug.println("P1 : " + P1) - debug.println("P1 <:< P = " + res) - - res - } - - def recur(X: Type, P: Type): Boolean = (X <:< P) || (P match { - case _: SingletonType => true - case _: TypeProxy - if isAbstract(P) => false - case defn.ArrayOf(tpT) => - X match { - case defn.ArrayOf(tpE) => recur(tpE, tpT) - case _ => recur(defn.AnyType, tpT) - } - case tpe: AppliedType => - // first try withou striping type parameters for performance - isClassDetermined(X, tpe)(ctx.fresh.setNewTyperState()) || - isClassDetermined(stripTypeParam(X), tpe)(ctx.fresh.setNewTyperState()) - case AndType(tp1, tp2) => recur(X, tp1) && recur(X, tp2) - case OrType(tp1, tp2) => recur(X, tp1) && recur(X, tp2) - case AnnotatedType(t, _) => recur(X, t) - case _: RefinedType => false - case _ => true - }) - - val res = recur(replaceX(X.widen), replaceP(P)) - - debug.println(i"checking ${X.show} isInstanceOf ${P} = $res") - - res - } -} diff --git a/compiler/src/dotty/tools/dotc/transform/Literalize.scala.disabled b/compiler/src/dotty/tools/dotc/transform/Literalize.scala.disabled index 8d2b06e24462..1b5d3d51a449 100644 --- a/compiler/src/dotty/tools/dotc/transform/Literalize.scala.disabled +++ b/compiler/src/dotty/tools/dotc/transform/Literalize.scala.disabled @@ -38,7 +38,7 @@ class Literalize extends MiniPhase { thisTransform => * Singleton type bounds (see SIP 23). Presumably * * object O1 { val x: Singleton = 42; println("43") } - * object O2 { inline val x = 42; println("43") } + * object O2 { transparent val x = 42; println("43") } * * should behave differently. * diff --git a/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala b/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala index 5c3dea9cb79e..8d561f5c916a 100644 --- a/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala @@ -59,6 +59,8 @@ abstract class MacroTransform extends Phase { transform(parents)(ctx.superCallContext), transformSelf(self), transformStats(impl.body, tree.symbol)) + case UntypedSplice(_) => + tree case _ => super.transform(tree) } diff --git a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala index 8e1981c85e07..37965cdf0872 100644 --- a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala +++ b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala @@ -360,7 +360,7 @@ class MegaPhase(val miniPhases: Array[MiniPhase]) extends Phase { case tree: Inlined => implicit val ctx = prepInlined(tree, start)(outerCtx) val bindings = transformSpecificTrees(tree.bindings, start) - val expansion = transformTree(tree.expansion, start) + val expansion = transformTree(tree.expansion, start)(inlineContext(tree.call)) goInlined(cpy.Inlined(tree)(tree.call, bindings, expansion), start) case tree: Return => implicit val ctx = prepReturn(tree, start)(outerCtx) diff --git a/compiler/src/dotty/tools/dotc/transform/Memoize.scala b/compiler/src/dotty/tools/dotc/transform/Memoize.scala index aee354a077c7..28dc2e8dd8fc 100644 --- a/compiler/src/dotty/tools/dotc/transform/Memoize.scala +++ b/compiler/src/dotty/tools/dotc/transform/Memoize.scala @@ -105,7 +105,7 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase => cpy.installAfter(thisPhase) } - val NoFieldNeeded = Lazy | Deferred | JavaDefined | (if (ctx.settings.YnoInline.value) EmptyFlags else Inline) + val NoFieldNeeded = Lazy | Deferred | JavaDefined | (if (ctx.settings.YnoInline.value) EmptyFlags else Transparent) def erasedBottomTree(sym: Symbol) = { if (sym eq defn.NothingClass) Throw(Literal(Constant(null))) diff --git a/compiler/src/dotty/tools/dotc/transform/MixinOps.scala b/compiler/src/dotty/tools/dotc/transform/MixinOps.scala index d3e08d7f9b7e..921e3762f186 100644 --- a/compiler/src/dotty/tools/dotc/transform/MixinOps.scala +++ b/compiler/src/dotty/tools/dotc/transform/MixinOps.scala @@ -44,7 +44,7 @@ class MixinOps(cls: ClassSymbol, thisPhase: DenotTransformer)(implicit ctx: Cont */ def isCurrent(sym: Symbol) = ctx.atPhase(thisPhase) { implicit ctx => - cls.info.member(sym.name).hasAltWith(_.symbol == sym) + cls.info.nonPrivateMember(sym.name).hasAltWith(_.symbol == sym) // this is a hot spot, where we spend several seconds while compiling stdlib // unfortunately it will discard and recompute all the member chaches, // both making itself slow and slowing down anything that runs after it diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 85e9af414d60..7d30e3260dd3 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -9,7 +9,7 @@ import Types._, Contexts._, Names._, Flags._, DenotTransformers._ import SymDenotations._, StdNames._, Annotations._, Trees._, Scopes._ import Decorators._ import Symbols._, SymUtils._ -import reporting.diagnostic.messages.{ImportRenamedTwice, NotAMember, SuperCallsNotAllowedInline} +import reporting.diagnostic.messages._ object PostTyper { val name = "posttyper" @@ -40,7 +40,7 @@ object PostTyper { * * (10) Adds Child annotations to all sealed classes * - * (11) Minimizes `call` fields of `Inline` nodes to just point to the toplevel + * (11) Minimizes `call` fields of `Inlined` nodes to just point to the toplevel * class from which code was inlined. * * The reason for making this a macro transform is that some functions (in particular @@ -156,7 +156,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase } } - /** 1. If we are an an inline method but not in a nested quote, mark the inline method + /** 1. If we are in a transparent method but not in a nested quote, mark the transparent method * as a macro. * * 2. If selection is a quote or splice node, record that fact in the current compilation unit. @@ -165,7 +165,10 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase def markAsMacro(c: Context): Unit = if (c.owner eq c.outer.owner) markAsMacro(c.outer) - else if (c.owner.isInlineableMethod) c.owner.setFlag(Macro) + else if (c.owner.isTransparentMethod) { + c.owner.setFlag(Macro) + c.owner.resetFlag(Erased) // FIXME: Macros should be Erased, but that causes problems right now + } else if (!c.outer.owner.is(Package)) markAsMacro(c.outer) if (sym.isSplice || sym.isQuote) { @@ -174,17 +177,22 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase } } + private object dropInlines extends TreeMap { + override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { + case Inlined(call, _, _) => Typed(call, TypeTree(tree.tpe)) + case _ => super.transform(tree) + } + } + override def transform(tree: Tree)(implicit ctx: Context): Tree = try tree match { case tree: Ident if !tree.isType => - checkNotErased(tree) handleMeta(tree.symbol) tree.tpe match { case tpe: ThisType => This(tpe.cls).withPos(tree.pos) case _ => tree } case tree @ Select(qual, name) => - checkNotErased(tree) handleMeta(tree.symbol) if (name.isTypeName) { Checking.checkRealizable(qual.tpe, qual.pos.focus) @@ -193,14 +201,24 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase else transformSelect(tree, Nil) case tree: Apply => - methPart(tree) match { + val methType = tree.fun.tpe.widen + val app = + if (methType.isErasedMethod) + tpd.cpy.Apply(tree)( + tree.fun, + tree.args.map(arg => + if (methType.isImplicitMethod && arg.pos.isSynthetic) ref(defn.Predef_undefined) + else dropInlines.transform(arg))) + else + tree + methPart(app) match { case Select(nu: New, nme.CONSTRUCTOR) if isCheckable(nu) => // need to check instantiability here, because the type of the New itself // might be a type constructor. Checking.checkInstantiable(tree.tpe, nu.pos) - withNoCheckNews(nu :: Nil)(super.transform(tree)) + withNoCheckNews(nu :: Nil)(super.transform(app)) case _ => - super.transform(tree) + super.transform(app) } case tree: TypeApply => val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree) @@ -216,7 +234,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase case _ => super.transform(tree1) } - case Inlined(call, bindings, expansion) => + case Inlined(call, bindings, expansion) if !call.isEmpty => // Leave only a call trace consisting of // - a reference to the top-level class from which the call was inlined, // - the call's position @@ -233,7 +251,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase val callTrace = if (call.symbol.is(Macro)) call else Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos) - cpy.Inlined(tree)(callTrace, transformSub(bindings), transform(expansion)) + cpy.Inlined(tree)(callTrace, transformSub(bindings), transform(expansion)(inlineContext(call))) case tree: Template => withNoCheckNews(tree.parents.flatMap(newPart)) { val templ1 = paramFwd.forwardParamAccessors(tree) @@ -241,9 +259,14 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase superAcc.wrapTemplate(templ1)( super.transform(_).asInstanceOf[Template])) } + case tree: ValDef => + val tree1 = cpy.ValDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol)) + transformMemberDef(tree1) + super.transform(tree1) case tree: DefDef => - transformMemberDef(tree) - superAcc.wrapDefDef(tree)(super.transform(tree).asInstanceOf[DefDef]) + val tree1 = cpy.DefDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol)) + transformMemberDef(tree1) + superAcc.wrapDefDef(tree1)(super.transform(tree1).asInstanceOf[DefDef]) case tree: TypeDef => transformMemberDef(tree) val sym = tree.symbol @@ -256,9 +279,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase tree } super.transform(tree) - case tree: MemberDef => - transformMemberDef(tree) - super.transform(tree) case tree: New if isCheckable(tree) => Checking.checkInstantiable(tree.tpe, tree.pos) super.transform(tree) @@ -317,13 +337,10 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase throw ex } - private def checkNotErased(tree: RefTree)(implicit ctx: Context): Unit = { - if (tree.symbol.is(Erased) && !ctx.mode.is(Mode.Type)) { - val msg = - if (tree.symbol.is(CaseAccessor)) "First parameter list of case class may not contain `erased` parameters" - else i"${tree.symbol} is declared as erased, but is in fact used" - ctx.error(msg, tree.pos) - } - } + /** Transforms the rhs tree into a its default tree if it is in an `erased` val/def. + * Performed to shrink the tree that is known to be erased later. + */ + private def normalizeErasedRhs(rhs: Tree, sym: Symbol)(implicit ctx: Context) = + if (sym.is(Erased, butNot = Deferred)) dropInlines.transform(rhs) else rhs } } diff --git a/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala b/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala new file mode 100644 index 000000000000..38a05099da02 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala @@ -0,0 +1,55 @@ +package dotty.tools.dotc +package transform + +import core._ +import Contexts._ +import DenotTransformers.SymTransformer +import Flags._ +import SymDenotations._ +import Symbols._ +import Types._ +import typer.RefChecks +import MegaPhase.MiniPhase +import ast.tpd + +/** This phase makes all erased term members of classes private so that they cannot + * conflict with non-erased members. This is needed so that subsequent phases like + * ResolveSuper that inspect class members work correctly. + * The phase also replaces all expressions that appear in an erased context by + * default values. This is necessary so that subsequent checking phases such + * as IsInstanceOfChecker don't give false negatives. + */ +class PruneErasedDefs extends MiniPhase with SymTransformer { thisTransform => + import tpd._ + + override def phaseName = PruneErasedDefs.name + + override def changesMembers = true // makes erased members private + + override def runsAfterGroupsOf = Set(RefChecks.name, ExplicitOuter.name) + + override def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = + if (sym.is(Erased, butNot = Private) && sym.owner.isClass) + sym.copySymDenotation( + //name = UnlinkedErasedName.fresh(sym.name.asTermName), + initFlags = sym.flags | Private) + else sym + + override def transformApply(tree: Apply)(implicit ctx: Context) = + if (tree.fun.tpe.widen.isErasedMethod) + cpy.Apply(tree)(tree.fun, tree.args.map(arg => ref(defn.Predef_undefined))) + else tree + + override def transformValDef(tree: ValDef)(implicit ctx: Context) = + if (tree.symbol.is(Erased) && !tree.rhs.isEmpty) + cpy.ValDef(tree)(rhs = ref(defn.Predef_undefined)) + else tree + + override def transformDefDef(tree: DefDef)(implicit ctx: Context) = + if (tree.symbol.is(Erased) && !tree.rhs.isEmpty) + cpy.DefDef(tree)(rhs = ref(defn.Predef_undefined)) + else tree +} +object PruneErasedDefs { + val name = "pruneErasedDefs" +} diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index e70a2985f5ff..6993ebf50abb 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -59,14 +59,14 @@ import dotty.tools.dotc.core.quoted._ * and then performs the same transformation on `'{ ... x1$1.unary_~ ... x2$1.unary_~ ...}`. * * - * For inline macro definitions we assume that we have a single ~ directly as the RHS. + * For transparent macro definitions we assume that we have a single ~ directly as the RHS. * We will transform the definition from * ``` - * inline def foo[T1, ...](inline x1: X, ..., y1: Y, ....): Z = ~{ ... T1 ... x ... '(y) ... } + * transparent def foo[T1, ...] (transparent x1: X, ..., y1: Y, ....): Z = ~{ ... T1 ... x ... '(y) ... } * ``` * to * ``` - * inline def foo[T1, ...](inline x1: X, ..., y1: Y, ....): Seq[Any] => Object = { (args: Seq[Any]) => { + * transparent def foo[T1, ...] (transparent x1: X, ..., y1: Y, ....): Seq[Any] => Object = { (args: Seq[Any]) => { * val T1$1 = args(0).asInstanceOf[Type[T1]] * ... * val x1$1 = args(0).asInstanceOf[X] @@ -76,8 +76,8 @@ import dotty.tools.dotc.core.quoted._ * { ... x1$1 .... '{ ... T1$1.unary_~ ... x1$1.toExpr.unary_~ ... y1$1.unary_~ ... } ... } * } * ``` - * Where `inline` parameters with type Boolean, Byte, Short, Int, Long, Float, Double, Char and String are - * passed as their actual runtime value. See `isStage0Value`. Other `inline` arguments such as functions are handled + * Where `transparent` parameters with type Boolean, Byte, Short, Int, Long, Float, Double, Char and String are + * passed as their actual runtime value. See `isStage0Value`. Other `transparent` arguments such as functions are handled * like `y1: Y`. * * Note: the parameters of `foo` are kept for simple overloading resolution but they are not used in the body of `foo`. @@ -243,7 +243,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { } /** Does the level of `sym` match the current level? - * An exception is made for inline vals in macros. These are also OK if their level + * An exception is made for transparent vals in macros. These are also OK if their level * is one higher than the current level, because on execution such values * are constant expression trees and we can pull out the constant from the tree. */ @@ -255,7 +255,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { !sym.is(Param) || levelOK(sym.owner) } - /** Issue a "splice outside quote" error unless we ar in the body of an inline method */ + /** Issue a "splice outside quote" error unless we ar in the body of a transparent method */ def spliceOutsideQuotes(pos: Position)(implicit ctx: Context): Unit = ctx.error(i"splice outside quotes", pos) @@ -432,7 +432,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { /** If inside a quote, split the body of the splice into a core and a list of embedded quotes * and make a hole from these parts. Otherwise issue an error, unless we - * are in the body of an inline method. + * are in the body of a transparent method. */ private def splice(splice: Select)(implicit ctx: Context): Tree = { if (level > 1) { @@ -582,7 +582,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { val last = enteredSyms stats.foreach(markDef) mapOverTree(last) - case Inlined(call, bindings, InlineSplice(expansion @ Select(body, name))) => + case Inlined(call, bindings, InlineSplice(expansion @ Select(body, name))) if !call.isEmpty => assert(call.symbol.is(Macro)) val tree2 = if (level == 0) { @@ -604,7 +604,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { tree.rhs match { case InlineSplice(_) => if (!tree.symbol.isStatic) - ctx.error("Inline macro method must be a static method.", tree.pos) + ctx.error("Transparent macro method must be a static method.", tree.pos) markDef(tree) val reifier = nested(isQuote = true) reifier.transform(tree) // Ignore output, we only need the its embedding @@ -614,12 +614,12 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { cpy.DefDef(tree)(tpt = TypeTree(macroReturnType), rhs = lambda) case _ => ctx.error( - """Malformed inline macro. + """Malformed transparent macro. | |Expected the ~ to be at the top of the RHS: - | inline def foo(...): Int = ~impl(...) + | transparent def foo(...): Int = ~impl(...) |or - | inline def foo(...): Int = ~{ + | transparent def foo(...): Int = ~{ | val x = 1 | impl(... x ...) | } @@ -632,7 +632,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { } } - /** Takes a reference to an inline parameter `tree` and lifts it to an Expr */ + /** Takes a reference to an transparent parameter `tree` and lifts it to an Expr */ private def liftInlineParamValue(tree: Tree)(implicit ctx: Context): Tree = { val tpSym = tree.tpe.widenDealias.classSymbol @@ -651,7 +651,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { } private def isStage0Value(sym: Symbol)(implicit ctx: Context): Boolean = - (sym.is(Inline) && sym.owner.is(Macro) && !defn.isFunctionType(sym.info)) || + (sym.is(Transparent) && sym.owner.is(Macro) && !defn.isFunctionType(sym.info)) || sym == defn.TastyTopLevelSplice_tastyContext // intrinsic value at stage 0 private def liftList(list: List[Tree], tpe: Type)(implicit ctx: Context): Tree = { @@ -677,9 +677,9 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = { /** Transforms the return type of - * inline def foo(...): X = ~(...) + * transparent def foo(...): X = ~(...) * to - * inline def foo(...): Seq[Any] => Expr[Any] = (args: Seq[Any]) => ... + * transparent def foo(...): Seq[Any] => Expr[Any] = (args: Seq[Any]) => ... */ def transform(tp: Type): Type = tp match { case tp: MethodType => MethodType(tp.paramNames, tp.paramInfos, transform(tp.resType)) diff --git a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala index 940ce9199a08..4a9e4409ffa6 100644 --- a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala +++ b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala @@ -51,7 +51,8 @@ class ResolveSuper extends MiniPhase with IdentityDenotTransformer { thisPhase = override def phaseName: String = ResolveSuper.name override def runsAfter = Set(ElimByName.name, // verified empirically, need to figure out what the reason is. - AugmentScala2Traits.name) + AugmentScala2Traits.name, + PruneErasedDefs.name) // Erased decls make `isCurrent` work incorrectly override def changesMembers = true // the phase adds super accessors and method forwarders @@ -90,8 +91,6 @@ class ResolveSuper extends MiniPhase with IdentityDenotTransformer { thisPhase = } else ddef } - - private val PrivateOrAccessorOrDeferred = Private | Accessor | Deferred } object ResolveSuper { diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index 98f8949bb2ad..7c4b942429f7 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -63,9 +63,9 @@ object Splicer { def liftArgs(tpe: Type, args: List[List[Tree]]): List[Any] = tpe match { case tp: MethodType => val args1 = args.head.zip(tp.paramInfos).map { - case (arg: Literal, tp) if tp.hasAnnotation(defn.InlineParamAnnot) => arg.const.value + case (arg: Literal, tp) if tp.hasAnnotation(defn.TransparentParamAnnot) => arg.const.value case (arg, tp) => - assert(!tp.hasAnnotation(defn.InlineParamAnnot)) + assert(!tp.hasAnnotation(defn.TransparentParamAnnot)) // Replace argument by its binding val arg1 = bindMap.getOrElse(arg, arg) new scala.quoted.Exprs.TastyTreeExpr(arg1) @@ -154,12 +154,12 @@ object Splicer { try clazz.getMethod(name.toString, paramClasses: _*) catch { case _: NoSuchMethodException => - val msg = s"Could not find inline macro method ${clazz.getCanonicalName}.$name with parameters $paramClasses$extraMsg" + val msg = em"Could not find macro method ${clazz.getCanonicalName}.$name with parameters ($paramClasses%, %)$extraMsg" throw new StopInterpretation(msg, pos) } } - private def extraMsg = ". The most common reason for that is that you cannot use inline macro implementations in the same compilation run that defines them" + private def extraMsg = ". The most common reason for that is that you cannot use transparent macro implementations in the same compilation run that defines them" private def stopIfRuntimeException[T](thunk: => T): T = { try thunk diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index e1c36b9395c3..5c5bbebb3504 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -259,6 +259,13 @@ class TreeChecker extends Phase with SymTransformer { case _ => } + /** Exclude from double definition checks any erased symbols that were + * made `private` in phase `UnlinkErasedDecls`. These symbols will be removed + * completely in phase `Erasure` if they are defined in a currently compiled unit. + */ + override def excludeFromDoubleDeclCheck(sym: Symbol)(implicit ctx: Context) = + sym.is(PrivateErased) && !sym.initial.is(Private) + override def typed(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): tpd.Tree = { val tpdTree = super.typed(tree, pt) checkIdentNotJavaClass(tpdTree) @@ -411,9 +418,9 @@ class TreeChecker extends Phase with SymTransformer { } } - override def typedCase(tree: untpd.CaseDef, pt: Type, selType: Type, gadtSyms: Set[Symbol])(implicit ctx: Context): CaseDef = { + override def typedCase(tree: untpd.CaseDef, selType: Type, pt: Type, gadtSyms: Set[Symbol])(implicit ctx: Context): CaseDef = { withPatSyms(tpd.patVars(tree.pat.asInstanceOf[tpd.Tree])) { - super.typedCase(tree, pt, selType, gadtSyms) + super.typedCase(tree, selType, pt, gadtSyms) } } @@ -505,4 +512,6 @@ object TreeChecker { tp } }.apply(tp0) + + private val PrivateErased = allOf(Private, Erased) } diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index 39ea428c5ac9..fa32333739cb 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -4,6 +4,7 @@ package transform import core._ import Contexts._, Symbols._, Types._, Constants._, StdNames._, Decorators._ import ast.Trees._ +import ast.untpd import Erasure.Boxing._ import TypeErasure._ import ValueClasses._ @@ -12,7 +13,7 @@ import core.Flags._ import util.Positions._ import reporting.diagnostic.messages.TypeTestAlwaysSucceeds import reporting.trace - +import config.Printers.{ transforms => debug } /** This transform normalizes type tests and type casts, * also replacing type tests with singleton argument type with reference equality check @@ -21,12 +22,122 @@ import reporting.trace * - have a reference type as receiver * - can be translated directly to machine instructions * - * * Unfortunately this phase ended up being not Y-checkable unless types are erased. A cast to an ConstantType(3) or x.type - * cannot be rewritten before erasure. + * cannot be rewritten before erasure. That's why TypeTestsCasts is called from Erasure. */ object TypeTestsCasts { import ast.tpd._ + import typer.Inferencing.maximizeType + import typer.ProtoTypes.constrained + + /** Whether `(x:X).isInstanceOf[P]` can be checked at runtime? + * + * First do the following substitution: + * (a) replace `T @unchecked` and pattern binder types (e.g., `_$1`) in P with WildcardType + * (b) replace pattern binder types (e.g., `_$1`) in X: + * - variance = 1 : hiBound + * - variance = -1 : loBound + * - variance = 0 : OrType(Any, Nothing) // TODO: use original type param bounds + * + * Then check: + * + * 1. if `X <:< P`, TRUE + * 2. if `P` is a singleton type, TRUE + * 3. if `P` refers to an abstract type member or type parameter, FALSE + * 4. if `P = Array[T]`, checkable(E, T) where `E` is the element type of `X`, defaults to `Any`. + * 5. if `P` is `pre.F[Ts]` and `pre.F` refers to a class which is not `Array`: + * (a) replace `Ts` with fresh type variables `Xs` + * (b) constrain `Xs` with `pre.F[Xs] <:< X` + * (c) instantiate Xs and check `pre.F[Xs] <:< P` + * 6. if `P = T1 | T2` or `P = T1 & T2`, checkable(X, T1) && checkable(X, T2). + * 7. if `P` is a refinement type, FALSE + * 8. otherwise, TRUE + */ + def checkable(X: Type, P: Type, pos: Position)(implicit ctx: Context): Boolean = { + def isAbstract(P: Type) = !P.dealias.typeSymbol.isClass + def isPatternTypeSymbol(sym: Symbol) = !sym.isClass && sym.is(Case) + + def replaceP(tp: Type)(implicit ctx: Context) = new TypeMap { + def apply(tp: Type) = tp match { + case tref: TypeRef + if isPatternTypeSymbol(tref.typeSymbol) => WildcardType + case AnnotatedType(_, annot) + if annot.symbol == defn.UncheckedAnnot => WildcardType + case _ => mapOver(tp) + } + }.apply(tp) + + def replaceX(tp: Type)(implicit ctx: Context) = new TypeMap { + def apply(tp: Type) = tp match { + case tref: TypeRef + if isPatternTypeSymbol(tref.typeSymbol) => + if (variance == 1) tref.info.hiBound + else if (variance == -1) tref.info.loBound + else OrType(defn.AnyType, defn.NothingType) + case _ => mapOver(tp) + } + }.apply(tp) + + /** Approximate type parameters depending on variance */ + def stripTypeParam(tp: Type)(implicit ctx: Context) = new ApproximatingTypeMap { + def apply(tp: Type): Type = tp match { + case tp: TypeRef if tp.underlying.isInstanceOf[TypeBounds] => + val lo = apply(tp.info.loBound) + val hi = apply(tp.info.hiBound) + range(lo, hi) + case _ => + mapOver(tp) + } + }.apply(tp) + + def isClassDetermined(X: Type, P: AppliedType)(implicit ctx: Context) = { + val AppliedType(tycon, _) = P + val typeLambda = tycon.ensureLambdaSub.asInstanceOf[TypeLambda] + val tvars = constrained(typeLambda, untpd.EmptyTree, alwaysAddTypeVars = true)._2.map(_.tpe) + val P1 = tycon.appliedTo(tvars) + + debug.println("P : " + P) + debug.println("P1 : " + P1) + debug.println("X : " + X) + + P1 <:< X // constraint P1 + + // use fromScala2x to avoid generating pattern bound symbols + maximizeType(P1, pos, fromScala2x = true) + + val res = P1 <:< P + debug.println("P1 : " + P1) + debug.println("P1 <:< P = " + res) + + res + } + + def recur(X: Type, P: Type): Boolean = (X <:< P) || (P match { + case _: SingletonType => true + case _: TypeProxy + if isAbstract(P) => false + case defn.ArrayOf(tpT) => + X match { + case defn.ArrayOf(tpE) => recur(tpE, tpT) + case _ => recur(defn.AnyType, tpT) + } + case tpe: AppliedType => + // first try withou striping type parameters for performance + isClassDetermined(X, tpe)(ctx.fresh.setNewTyperState()) || + isClassDetermined(stripTypeParam(X), tpe)(ctx.fresh.setNewTyperState()) + case AndType(tp1, tp2) => recur(X, tp1) && recur(X, tp2) + case OrType(tp1, tp2) => recur(X, tp1) && recur(X, tp2) + case AnnotatedType(t, _) => recur(X, t) + case _: RefinedType => false + case _ => true + }) + + val res = recur(replaceX(X.widen), replaceP(P)) + + debug.println(i"checking ${X.show} isInstanceOf ${P} = $res") + + res + } def interceptTypeApply(tree: TypeApply)(implicit ctx: Context): Tree = trace(s"transforming ${tree.show}", show = true) { tree.fun match { @@ -156,8 +267,12 @@ object TypeTestsCasts { transformIsInstanceOf(expr, erasure(testType), flagUnrelated) } - if (sym.isTypeTest) + if (sym.isTypeTest) { + val argType = tree.args.head.tpe + if (!checkable(expr.tpe, argType, tree.pos)) + ctx.warning(s"the type test for $argType cannot be checked at runtime", tree.pos) transformTypeTest(expr, tree.args.head.tpe, flagUnrelated = true) + } else if (sym eq defn.Any_asInstanceOf) transformAsInstanceOf(erasure(tree.args.head.tpe)) else tree diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index bcf5493396b0..c74c0a1d05c8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -243,7 +243,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => case methType: MethodType => // apply the result type constraint, unless method type is dependent val resultApprox = resultTypeApprox(methType) - if (!constrainResult(resultApprox, resultType)) + if (!constrainResult(methRef.symbol, resultApprox, resultType)) if (ctx.typerState.isCommittable) // defer the problem until after the application; // it might be healed by an implicit conversion @@ -708,7 +708,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => // help sharpen the inferred parameter types for the argument function literal(s). // This tweak is needed to make i1378 compile. if (tree.args.exists(untpd.isFunctionWithUnknownParamType(_))) - if (!constrainResult(fun1.tpe.widen, proto.derivedFunProto(resultType = pt))) + if (!constrainResult(tree.symbol, fun1.tpe.widen, proto.derivedFunProto(resultType = pt))) typr.println(i"result failure for $tree with type ${fun1.tpe.widen}, expected = $pt") /** Type application where arguments come from prototype, and no implicits are inserted */ @@ -738,7 +738,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => } fun1.tpe match { - case err: ErrorType => untpd.cpy.Apply(tree)(fun1, proto.typedArgs).withType(err) + case err: ErrorType => cpy.Apply(tree)(fun1, proto.typedArgs).withType(err) case TryDynamicCallType => typedDynamicApply(tree, pt) case _ => if (originalProto.isDropped) fun1 @@ -777,31 +777,34 @@ trait Applications extends Compatibility { self: Typer with Dynamic => wrapDefs(liftedDefs, typed(assign)) } - if (untpd.isOpAssign(tree)) - tryEither { - implicit ctx => realApply - } { (failedVal, failedState) => + val app1 = + if (untpd.isOpAssign(tree)) tryEither { - implicit ctx => typedOpAssign - } { (_, _) => - failedState.commit() - failedVal + implicit ctx => realApply + } { (failedVal, failedState) => + tryEither { + implicit ctx => typedOpAssign + } { (_, _) => + failedState.commit() + failedVal + } } + else { + val app = realApply + app match { + case Apply(fn @ Select(left, _), right :: Nil) if fn.hasType => + val op = fn.symbol + if (op == defn.Any_== || op == defn.Any_!=) + checkCanEqual(left.tpe.widen, right.tpe.widen, app.pos) + case _ => + } + app } - else { - val app = realApply - app match { - case Apply(fn @ Select(left, _), right :: Nil) if fn.hasType => - val op = fn.symbol - if (op == defn.Any_== || op == defn.Any_!=) - checkCanEqual(left.tpe.widen, right.tpe.widen, app.pos) - case _ => - } - app match { - case Apply(fun, args) if fun.tpe.widen.isErasedMethod => - tpd.cpy.Apply(app)(fun = fun, args = args.map(arg => defaultValue(arg.tpe))) - case _ => app - } + app1 match { + case Apply(Block(stats, fn), args) => + tpd.cpy.Block(app1)(stats, tpd.cpy.Apply(app1)(fn, args)) + case _ => + app1 } } @@ -1280,12 +1283,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * section conforms to the expected type `resultType`? If `resultType` * is a `IgnoredProto`, pick the underlying type instead. */ - def resultConforms(alt: Type, resultType: Type)(implicit ctx: Context): Boolean = resultType match { - case IgnoredProto(ignored) => resultConforms(alt, ignored) + def resultConforms(altSym: Symbol, altType: Type, resultType: Type)(implicit ctx: Context): Boolean = resultType match { + case IgnoredProto(ignored) => resultConforms(altSym, altType, ignored) case _: ValueType => - alt.widen match { - case tp: PolyType => resultConforms(constrained(tp).resultType, resultType) - case tp: MethodType => constrainResult(tp.resultType, resultType) + altType.widen match { + case tp: PolyType => resultConforms(altSym, constrained(tp).resultType, resultType) + case tp: MethodType => constrainResult(altSym, tp.resultType, resultType) case _ => true } case _ => true @@ -1304,9 +1307,9 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * do they prune much, on average. */ def adaptByResult(chosen: TermRef) = pt match { - case pt: FunProto if !ctx.test(implicit ctx => resultConforms(chosen, pt.resultType)) => + case pt: FunProto if !ctx.test(implicit ctx => resultConforms(chosen.symbol, chosen, pt.resultType)) => val conformingAlts = alts.filter(alt => - (alt ne chosen) && ctx.test(implicit ctx => resultConforms(alt, pt.resultType))) + (alt ne chosen) && ctx.test(implicit ctx => resultConforms(alt.symbol, alt, pt.resultType))) conformingAlts match { case Nil => chosen case alt2 :: Nil => alt2 @@ -1567,12 +1570,6 @@ trait Applications extends Compatibility { self: Typer with Dynamic => harmonizedElems } - /** Transforms the rhs tree into a its default tree if it is in an `erased` val/def. - * Performed to shrink the tree that is known to be erased later. - */ - protected def normalizeErasedRhs(rhs: Tree, sym: Symbol)(implicit ctx: Context) = - if (sym.is(Erased) && rhs.tpe.exists) defaultValue(rhs.tpe) else rhs - /** If all `types` are numeric value types, and they are not all the same type, * pick a common numeric supertype and widen any constant types in `tpes` to it. * If the resulting types are all the same, return them instead of the original ones. diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 47893a9390c1..ca2dd9b3c1dd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -369,8 +369,11 @@ object Checking { if (!ok && !sym.is(Synthetic)) fail(i"modifier `$flag` is not allowed for this definition") - if (sym.is(Inline) && ((sym.is(ParamAccessor) && sym.owner.isClass) || sym.is(TermParam) && sym.owner.isClassConstructor)) - fail(ParamsNoInline(sym.owner)) + if (sym.is(Transparent) && + ( sym.is(ParamAccessor) && sym.owner.isClass + || sym.is(TermParam) && !sym.owner.isTransparentMethod + )) + fail(ParamsNoTransparent(sym.owner)) if (sym.is(ImplicitCommon)) { if (sym.owner.is(Package)) @@ -396,18 +399,17 @@ object Checking { fail(OnlyClassesCanHaveDeclaredButUndefinedMembers(sym)) checkWithDeferred(Private) checkWithDeferred(Final) - checkWithDeferred(Inline) + checkWithDeferred(Transparent) } if (sym.isValueClass && sym.is(Trait) && !sym.isRefinementClass) fail(CannotExtendAnyVal(sym)) checkCombination(Final, Sealed) checkCombination(Private, Protected) checkCombination(Abstract, Override) - checkCombination(Lazy, Inline) - checkCombination(Module, Inline) - checkCombination(Transparent, Inline) + checkCombination(Lazy, Transparent) + checkCombination(Module, Transparent) checkNoConflict(Lazy, ParamAccessor, s"parameter may not be `lazy`") - if (sym.is(Inline)) checkApplicable(Inline, sym.isTerm && !sym.is(Mutable | Module)) + if (sym.is(Transparent)) checkApplicable(Transparent, sym.isTerm && !sym.is(Mutable | Module)) if (sym.is(Lazy)) checkApplicable(Lazy, !sym.is(Method | Mutable)) if (sym.isType && !sym.is(Deferred)) for (cls <- sym.allOverriddenSymbols.filter(_.isClass)) { @@ -666,21 +668,21 @@ trait Checking { } } - /** Check that `tree` can be marked `inline` */ - def checkInlineConformant(tree: Tree, isFinal: Boolean, what: => String)(implicit ctx: Context): Unit = { - // final vals can be marked inline even if they're not pure, see Typer#patchFinalVals + /** Check that `tree` can right hand-side or argument to `transparent` value or parameter. */ + def checkTransparentConformant(tree: Tree, isFinal: Boolean, what: => String)(implicit ctx: Context): Unit = { + // final vals can be marked transparent even if they're not pure, see Typer#patchFinalVals val purityLevel = if (isFinal) Idempotent else Pure - tree.tpe match { - case tp: TermRef if tp.symbol.is(InlineParam) => // ok - case tp => tp.widenTermRefExpr match { - case tp: ConstantType if exprPurity(tree) >= purityLevel => // ok - case _ => - val allow = ctx.erasedTypes || ctx.owner.ownersIterator.exists(_.isInlineableMethod) - if (!allow) ctx.error(em"$what must be a constant expression", tree.pos) - } + tree.tpe.widenTermRefExpr match { + case tp: ConstantType if exprPurity(tree) >= purityLevel => // ok + case _ => + val allow = ctx.erasedTypes || ctx.inTransparentMethod + if (!allow) ctx.error(em"$what must be a constant expression", tree.pos) } } + /** A hook to exclude selected symbols from double declaration check */ + def excludeFromDoubleDeclCheck(sym: Symbol)(implicit ctx: Context) = false + /** Check that class does not declare same symbol twice */ def checkNoDoubleDeclaration(cls: Symbol)(implicit ctx: Context): Unit = { val seen = new mutable.HashMap[Name, List[Symbol]] { @@ -691,7 +693,7 @@ trait Checking { def checkDecl(decl: Symbol): Unit = { for (other <- seen(decl.name)) { typr.println(i"conflict? $decl $other") - if (decl.matches(other)) { + if (decl.matches(other) /*&& !decl.is(Erased) && !other.is(Erased)*/) { def doubleDefError(decl: Symbol, other: Symbol): Unit = if (!decl.info.isErroneous && !other.info.isErroneous) ctx.error(DoubleDeclaration(decl, other), decl.pos) @@ -703,7 +705,8 @@ trait Checking { decl resetFlag HasDefaultParams } } - seen(decl.name) = decl :: seen(decl.name) + if (!excludeFromDoubleDeclCheck(decl)) + seen(decl.name) = decl :: seen(decl.name) } cls.info.decls.foreach(checkDecl) @@ -942,7 +945,7 @@ trait NoChecking extends ReChecking { override def checkImplicitConversionDefOK(sym: Symbol)(implicit ctx: Context): Unit = () override def checkImplicitConversionUseOK(sym: Symbol, pos: Position)(implicit ctx: Context): Unit = () override def checkFeasibleParent(tp: Type, pos: Position, where: => String = "")(implicit ctx: Context): Type = tp - override def checkInlineConformant(tree: Tree, isFinal: Boolean, what: => String)(implicit ctx: Context) = () + override def checkTransparentConformant(tree: Tree, isFinal: Boolean, what: => String)(implicit ctx: Context) = () override def checkNoDoubleDeclaration(cls: Symbol)(implicit ctx: Context): Unit = () override def checkParentCall(call: Tree, caller: ClassSymbol)(implicit ctx: Context) = () override def checkSimpleKinded(tpt: Tree)(implicit ctx: Context): Tree = tpt diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 4ba82bc897b9..c3e5ab431213 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -99,9 +99,9 @@ object Implicits { mt.isImplicitMethod || mt.paramInfos.length != 1 || !ctx.test(implicit ctx => - argType relaxed_<:< wildApprox(widenSingleton(mt.paramInfos.head), null, Set.empty)) + argType relaxed_<:< wildApprox(widenSingleton(mt.paramInfos.head))) case rtp => - discardForView(wildApprox(rtp, null, Set.empty), argType) + discardForView(wildApprox(rtp), argType) } case tpw: TermRef => false // can't discard overloaded refs @@ -900,7 +900,7 @@ trait Implicits { self: Typer => } /** The expected type where parameters and uninstantiated typevars are replaced by wildcard types */ - val wildProto = implicitProto(pt, wildApprox(_, null, Set.empty)) + val wildProto = implicitProto(pt, wildApprox(_)) val isNot = wildProto.classSymbol == defn.NotClass @@ -1132,19 +1132,24 @@ trait Implicits { self: Typer => val eligible = if (contextual) ctx.implicits.eligible(wildProto) else implicitScope(wildProto).eligible - searchImplicits(eligible, contextual).recoverWith { - failure => failure.reason match { - case _: AmbiguousImplicits => failure - case reason => - if (contextual) - bestImplicit(contextual = false).recoverWith { - failure2 => reason match { - case (_: DivergingImplicit) | (_: ShadowedImplicit) => failure - case _ => failure2 + searchImplicits(eligible, contextual) match { + case result: SearchSuccess => + if (contextual && ctx.mode.is(Mode.TransparentBody)) + PrepareTransparent.markContextualImplicit(result.tree) + result + case failure: SearchFailure => + failure.reason match { + case _: AmbiguousImplicits => failure + case reason => + if (contextual) + bestImplicit(contextual = false).recoverWith { + failure2 => reason match { + case (_: DivergingImplicit) | (_: ShadowedImplicit) => failure + case _ => failure2 + } } - } - else failure - } + else failure + } } } diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index f0611878b666..151aae7ff77e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -185,6 +185,8 @@ object Inferencing { * * Invariant refinement can be assumed if `PatternType`'s class(es) are final or * case classes (because of `RefChecks#checkCaseClassInheritanceInvariant`). + * + * TODO: Update so that GADT symbols can be variant, and we special case final class types in patterns */ def constrainPatternType(tp: Type, pt: Type)(implicit ctx: Context): Boolean = { def refinementIsInvariant(tp: Type): Boolean = tp match { @@ -408,7 +410,7 @@ trait Inferencing { this: Typer => val resultAlreadyConstrained = tree.isInstanceOf[Apply] || tree.tpe.isInstanceOf[MethodOrPoly] if (!resultAlreadyConstrained) - constrainResult(tree.tpe, pt) + constrainResult(tree.symbol, tree.tpe, pt) // This is needed because it could establish singleton type upper bounds. See i2998.scala. val tp = tree.tpe.widen diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 82e314050cd0..516ded001526 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -15,7 +15,7 @@ import StdNames.nme import Contexts.Context import Names.{Name, TermName, EmptyTermName} import NameOps._ -import NameKinds.{ClassifiedNameKind, InlineAccessorName, UniqueInlineName} +import NameKinds.{ClassifiedNameKind, InlineAccessorName, UniqueInlineName, TransparentScrutineeName, TransparentBinderName} import ProtoTypes.selectionProto import SymDenotations.SymDenotation import Annotations._ @@ -25,217 +25,29 @@ import config.Printers.inlining import ErrorReporting.errorTree import collection.mutable import transform.TypeUtils._ +import transform.SymUtils._ import reporting.trace import util.Positions.Position +import util.Property +import ast.TreeInfo object Inliner { import tpd._ - class InlineAccessors extends AccessProxies { - - /** If an inline accessor name wraps a unique inline name, this is taken as indication - * that the inline accessor takes its receiver as first parameter. Such accessors - * are created by MakeInlineablePassing. - */ - override def passReceiverAsArg(name: Name)(implicit ctx: Context) = name match { - case InlineAccessorName(UniqueInlineName(_, _)) => true - case _ => false - } - - /** A tree map which inserts accessors for non-public term members accessed from inlined code. - */ - abstract class MakeInlineableMap(val inlineSym: Symbol) extends TreeMap with Insert { - def accessorNameKind = InlineAccessorName - - /** A definition needs an accessor if it is private, protected, or qualified private - * and it is not part of the tree that gets inlined. The latter test is implemented - * by excluding all symbols properly contained in the inlined method. - * - * Constant vals don't need accessors since they are inlined in FirstTransform. - */ - def needsAccessor(sym: Symbol)(implicit ctx: Context) = - sym.isTerm && - (sym.is(AccessFlags) || sym.privateWithin.exists) && - !sym.isContainedIn(inlineSym) && - !(sym.isStable && sym.info.widenTermRefExpr.isInstanceOf[ConstantType]) - - def preTransform(tree: Tree)(implicit ctx: Context): Tree - - def postTransform(tree: Tree)(implicit ctx: Context) = tree match { - case Assign(lhs, rhs) if lhs.symbol.name.is(InlineAccessorName) => - cpy.Apply(tree)(useSetter(lhs), rhs :: Nil) - case _ => - tree - } - - override def transform(tree: Tree)(implicit ctx: Context): Tree = - postTransform(super.transform(preTransform(tree))) - } - - /** Direct approach: place the accessor with the accessed symbol. This has the - * advantage that we can re-use the receiver as is. But it is only - * possible if the receiver is essentially this or an outer this, which is indicated - * by the test that we can find a host for the accessor. - */ - class MakeInlineableDirect(inlineSym: Symbol) extends MakeInlineableMap(inlineSym) { - def preTransform(tree: Tree)(implicit ctx: Context): Tree = tree match { - case tree: RefTree if needsAccessor(tree.symbol) => - if (tree.symbol.isConstructor) { - ctx.error("Implementation restriction: cannot use private constructors in inline methods", tree.pos) - tree // TODO: create a proper accessor for the private constructor - } - else useAccessor(tree) - case _ => - tree - } - override def ifNoHost(reference: RefTree)(implicit ctx: Context): Tree = reference - } - - /** Fallback approach if the direct approach does not work: Place the accessor method - * in the same class as the inlined method, and let it take the receiver as parameter. - * This is tricky, since we have to find a suitable type for the parameter, which might - * require additional type parameters for the inline accessor. An example is in the - * `TestPassing` class in test `run/inline/inlines_1`: - * - * class C[T](x: T) { - * private[inlines] def next[U](y: U): (T, U) = (x, y) - * } - * class TestPassing { - * inline def foo[A](x: A): (A, Int) = { - * val c = new C[A](x) - * c.next(1) - * } - * inline def bar[A](x: A): (A, String) = { - * val c = new C[A](x) - * c.next("") - * } - * - * `C` could be compiled separately, so we cannot place the inline accessor in it. - * Instead, the inline accessor goes into `TestPassing` and takes the actual receiver - * type as argument: - * - * def inline$next$i1[A, U](x$0: C[A])(y: U): (A, U) = - * x$0.next[U](y) - * - * Since different calls might have different receiver types, we need to generate one - * such accessor per call, so they need to have unique names. - */ - class MakeInlineablePassing(inlineSym: Symbol) extends MakeInlineableMap(inlineSym) { - - def preTransform(tree: Tree)(implicit ctx: Context): Tree = tree match { - case _: Apply | _: TypeApply | _: RefTree - if needsAccessor(tree.symbol) && tree.isTerm && !tree.symbol.isConstructor => - val (refPart, targs, argss) = decomposeCall(tree) - val qual = qualifier(refPart) - inlining.println(i"adding receiver passing inline accessor for $tree/$refPart -> (${qual.tpe}, $refPart: ${refPart.getClass}, [$targs%, %], ($argss%, %))") - - // Need to dealias in order to cagtch all possible references to abstracted over types in - // substitutions - val dealiasMap = new TypeMap { - def apply(t: Type) = mapOver(t.dealias) - } - val qualType = dealiasMap(qual.tpe.widen) - - // The types that are local to the inlined method, and that therefore have - // to be abstracted out in the accessor, which is external to the inlined method - val localRefs = qualType.namedPartsWith(ref => - ref.isType && ref.symbol.isContainedIn(inlineSym)).toList - - // Add qualifier type as leading method argument to argument `tp` - def addQualType(tp: Type): Type = tp match { - case tp: PolyType => tp.derivedLambdaType(tp.paramNames, tp.paramInfos, addQualType(tp.resultType)) - case tp: ExprType => addQualType(tp.resultType) - case tp => MethodType(qualType.simplified :: Nil, tp) - } - - // Abstract accessed type over local refs - def abstractQualType(mtpe: Type): Type = - if (localRefs.isEmpty) mtpe - else PolyType.fromParams(localRefs.map(_.symbol.asType), mtpe) - .asInstanceOf[PolyType].flatten - - val accessed = refPart.symbol.asTerm - val accessedType = refPart.tpe.widen - val accessor = accessorSymbol( - owner = inlineSym.owner, - accessorName = InlineAccessorName(UniqueInlineName.fresh(accessed.name)), - accessorInfo = abstractQualType(addQualType(dealiasMap(accessedType))), - accessed = accessed) - - ref(accessor) - .appliedToTypeTrees(localRefs.map(TypeTree(_)) ++ targs) - .appliedToArgss((qual :: Nil) :: argss) - .withPos(tree.pos) - - // TODO: Handle references to non-public types. - // This is quite tricky, as such types can appear anywhere, including as parts - // of types of other things. For the moment we do nothing and complain - // at the implicit expansion site if there's a reference to an inaccessible type. - // Draft code (incomplete): - // - // val accessor = accessorSymbol(tree, TypeAlias(tree.tpe)).asType - // myAccessors += TypeDef(accessor).withPos(tree.pos.focus) - // ref(accessor).withPos(tree.pos) - // - case _ => tree - } - } - - /** Adds accessors for all non-public term members accessed - * from `tree`. Non-public type members are currently left as they are. - * This means that references to a private type will lead to typing failures - * on the code when it is inlined. Less than ideal, but hard to do better (see below). - * - * @return If there are accessors generated, a thicket consisting of the rewritten `tree` - * and all accessors, otherwise the original tree. - */ - def makeInlineable(tree: Tree)(implicit ctx: Context) = { - val inlineSym = ctx.owner - if (inlineSym.owner.isTerm) - // Inline methods in local scopes can only be called in the scope they are defined, - // so no accessors are needed for them. - tree - else - new MakeInlineablePassing(inlineSym).transform( - new MakeInlineableDirect(inlineSym).transform(tree)) - } - } - - def isLocal(sym: Symbol, inlineMethod: Symbol)(implicit ctx: Context) = - sym.isContainedIn(inlineMethod) && - sym != inlineMethod && - (!sym.is(Param) || sym.owner != inlineMethod) - - /** Register inline info for given inline method `sym`. - * - * @param sym The symbol denotatioon of the inline method for which info is registered - * @param treeExpr A function that computes the tree to be inlined, given a context - * This tree may still refer to non-public members. - * @param ctx The context to use for evaluating `treeExpr`. It needs - * to have the inlined method as owner. + /** A key to be used in a context property that provides a map from enclosing implicit + * value bindings to their right hand sides. */ - def registerInlineInfo( - 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 => - case _ => - if (!ctx.isAfterTyper) { - val inlineCtx = ctx - inlined.updateAnnotation(LazyBodyAnnotation { _ => - implicit val ctx = inlineCtx - val body = treeExpr(ctx) - if (ctx.reporter.hasErrors) body else ctx.compilationUnit.inlineAccessors.makeInlineable(body) - }) - } - } - } + private val InlineBindings = new Property.Key[MutableSymbolMap[Tree]] - /** `sym` has an inline method with a known body to inline (note: definitions coming + /** A map from the symbols of all enclosing inline value bindings to their right hand sides */ + def inlineBindings(implicit ctx: Context): MutableSymbolMap[Tree] = + ctx.property(InlineBindings).get + + /** `sym` has a transparent method with a known body to inline (note: definitions coming * from Scala2x class files might be `@forceInline`, but still lack that body. */ def hasBodyToInline(sym: SymDenotation)(implicit ctx: Context): Boolean = - sym.isInlinedMethod && sym.hasAnnotation(defn.BodyAnnot) // TODO: Open this up for transparent methods as well + sym.isTransparentInlineable && sym.hasAnnotation(defn.BodyAnnot) /** The body to inline for method `sym`. * @pre hasBodyToInline(sym) @@ -243,7 +55,29 @@ object Inliner { def bodyToInline(sym: SymDenotation)(implicit ctx: Context): Tree = sym.unforcedAnnotation(defn.BodyAnnot).get.tree - /** Try to inline a call to a `inline` method. Fail with error if the maximal + /** Should call with method `meth` be inlined in this context? */ + def isInlineable(meth: Symbol)(implicit ctx: Context): Boolean = { + + def suppressInline = + ctx.inTransparentMethod || + ctx.settings.YnoInline.value || + ctx.isAfterTyper || + ctx.reporter.hasErrors + + hasBodyToInline(meth) && !suppressInline + } + + /** Should call be inlined in this context? */ + def isInlineable(tree: Tree)(implicit ctx: Context): Boolean = tree match { + case Block(_, expr) => isInlineable(expr) + case _ => isInlineable(tree.symbol) + } + + /** Is `meth` a transparent method that should be inlined in this context? */ + def isTransparentInlineable(meth: Symbol)(implicit ctx: Context): Boolean = + meth.isTransparentInlineable && isInlineable(meth) + + /** Try to inline a call to a transparent method. Fail with error if the maximal * inline depth is exceeded. * * @param tree The call to inline @@ -251,17 +85,27 @@ object Inliner { * @return An `Inlined` node that refers to the original call and the inlined bindings * and body that replace it. */ - def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = - if (enclosingInlineds.length < ctx.settings.XmaxInlines.value) { + def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = tree match { + case Block(stats, expr) => + cpy.Block(tree)(stats, inlineCall(expr, pt)) + case _ if (enclosingInlineds.length < ctx.settings.XmaxInlines.value) => val body = bodyToInline(tree.symbol) // can typecheck the tree and thereby produce errors - if (ctx.reporter.hasErrors) tree else new Inliner(tree, body).inlined(pt) - } - else errorTree( - tree, - i"""|Maximal number of successive inlines (${ctx.settings.XmaxInlines.value}) exceeded, - |Maybe this is caused by a recursive inline method? - |You can use -Xmax:inlines to change the limit.""" - ) + if (ctx.reporter.hasErrors) tree + else { + val inlinerCtx = + if (ctx.property(InlineBindings).isDefined) ctx + else ctx.fresh.setProperty(InlineBindings, newMutableSymbolMap[Tree]) + new Inliner(tree, body)(inlinerCtx).inlined(pt) + } + case _ => + errorTree( + tree, + i"""|Maximal number of successive inlines (${ctx.settings.XmaxInlines.value}) exceeded, + |Maybe this is caused by a recursive transparent method? + |You can use -Xmax:inlines to change the limit.""", + (tree :: enclosingInlineds).last.pos + ) + } /** Replace `Inlined` node by a block that contains its bindings and expansion */ def dropInlined(inlined: tpd.Inlined)(implicit ctx: Context): Tree = { @@ -272,33 +116,25 @@ object Inliner { } tpd.seq(inlined.bindings, reposition.transform(inlined.expansion)) } - - /** The qualifier part of a Select or Ident. - * For an Ident, this is the `This` of the current class. (TODO: use elsewhere as well?) - */ - private def qualifier(tree: Tree)(implicit ctx: Context) = tree match { - case Select(qual, _) => qual - case _ => This(ctx.owner.enclosingClass.asClass) - } } /** Produces an inlined version of `call` via its `inlined` method. * * @param call the original call to an `inline` method - * @param rhsToInline the body of the inline method that replaces the call. + * @param rhsToInline the body of the transparent method that replaces the call. */ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { import tpd._ import Inliner._ - private val (methPart, targs, argss) = decomposeCall(call) - private val meth = methPart.symbol - private val prefix = qualifier(methPart) + private val (methPart, callTypeArgs, callValueArgss) = decomposeCall(call) + private val inlinedMethod = methPart.symbol + private val inlineCallPrefix = qualifier(methPart) // Make sure all type arguments to the call are fully determined - for (targ <- targs) fullyDefinedType(targ.tpe, "inlined type argument", targ.pos) + for (targ <- callTypeArgs) fullyDefinedType(targ.tpe, "inlined type argument", targ.pos) - /** A map from parameter names of the inline method to references of the actual arguments. + /** A map from parameter names of the transparent method to references of the actual arguments. * For a type argument this is the full argument type. * For a value argument, it is a reference to either the argument value * (if the argument is a pure expression of singleton type), or to `val` or `def` acting @@ -306,7 +142,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { */ private val paramBinding = new mutable.HashMap[Name, Type] - /** A map from references to (type and value) parameters of the inline method + /** A map from references to (type and value) parameters of the transparent method * to their corresponding argument or proxy references, as given by `paramBinding`. */ private val paramProxy = new mutable.HashMap[Type, Type] @@ -327,9 +163,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { private val thisProxy = new mutable.HashMap[ClassSymbol, TermRef] /** A buffer for bindings that define proxies for actual arguments */ - val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] - - computeParamBindings(meth.info, targs, argss) + private val bindingsBuf = new mutable.ListBuffer[MemberDef] private def newSym(name: Name, flags: FlagSet, info: Type): Symbol = ctx.newSymbol(ctx.owner, name, flags, info, coord = call.pos) @@ -344,10 +178,10 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { * @param bindingsBuf the buffer to which the definition should be appended */ private def paramBindingDef(name: Name, paramtp: Type, arg: Tree, - bindingsBuf: mutable.ListBuffer[ValOrDefDef]): ValOrDefDef = { + bindingsBuf: mutable.ListBuffer[MemberDef]): MemberDef = { val argtpe = arg.tpe.dealiasKeepAnnots val isByName = paramtp.dealias.isInstanceOf[ExprType] - val inlineFlag = if (paramtp.hasAnnotation(defn.InlineParamAnnot)) Inline else EmptyFlags + val inlineFlag = if (paramtp.hasAnnotation(defn.TransparentParamAnnot)) Transparent else EmptyFlags val (bindingFlags, bindingType) = if (isByName) (Method, ExprType(argtpe.widen)) else (inlineFlag, argtpe.widen) @@ -382,9 +216,40 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { assert(argss.isEmpty) } + // Compute val-definitions for all this-proxies and append them to `bindingsBuf` + private def computeThisBindings() = { + // All needed this-proxies, paired-with and sorted-by nesting depth of + // the classes they represent (innermost first) + val sortedProxies = thisProxy.toList.map { + case (cls, proxy) => + // The class that the this-proxy `selfSym` represents + def classOf(selfSym: Symbol) = selfSym.info.widen.classSymbol + // The total nesting depth of the class represented by `selfSym`. + def outerLevel(selfSym: Symbol): Int = classOf(selfSym).ownersIterator.length + (outerLevel(cls), proxy.symbol) + }.sortBy(-_._1) + + var lastSelf: Symbol = NoSymbol + var lastLevel: Int = 0 + for ((level, selfSym) <- sortedProxies) { + lazy val rhsClsSym = selfSym.info.widenDealias.classSymbol + val rhs = + if (lastSelf.exists) + ref(lastSelf).outerSelect(lastLevel - level, selfSym.info) + else if (rhsClsSym.is(Module) && rhsClsSym.isStatic) + ref(rhsClsSym.sourceModule) + else + inlineCallPrefix + bindingsBuf += ValDef(selfSym.asTerm, rhs) + inlining.println(i"proxy at $level: $selfSym = ${bindingsBuf.last}") + lastSelf = selfSym + lastLevel = level + } + } + private def canElideThis(tpe: ThisType): Boolean = - prefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls) || - tpe.cls.isContainedIn(meth) || + inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls) || + tpe.cls.isContainedIn(inlinedMethod) || tpe.cls.is(Package) /** Populate `thisProxy` and `paramProxy` as follows: @@ -402,13 +267,12 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { private def registerType(tpe: Type): Unit = tpe match { case tpe: ThisType if !canElideThis(tpe) && !thisProxy.contains(tpe.cls) => val proxyName = s"${tpe.cls.name}_this".toTermName - val proxyType = tpe.asSeenFrom(prefix.tpe, meth.owner) + val proxyType = tpe.asSeenFrom(inlineCallPrefix.tpe, inlinedMethod.owner) thisProxy(tpe.cls) = newSym(proxyName, Synthetic, proxyType).termRef if (!tpe.cls.isStaticOwner) - registerType(meth.owner.thisType) // make sure we have a base from which to outer-select + registerType(inlinedMethod.owner.thisType) // make sure we have a base from which to outer-select case tpe: NamedType - if tpe.symbol.is(Param) && tpe.symbol.owner == meth && - !paramProxy.contains(tpe) => + if tpe.symbol.is(Param) && tpe.symbol.owner == inlinedMethod && !paramProxy.contains(tpe) => paramProxy(tpe) = paramBinding(tpe.name) case _ => } @@ -420,132 +284,424 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case _ => } + /** Make `tree` part of inlined expansion. This means its owner has to be changed + * from its `originalOwner`, and, if it comes from outside the inlined method + * itself, it has to be marked as an inlined argument. + */ + def integrate(tree: Tree, originalOwner: Symbol)(implicit ctx: Context) = { + val result = tree.changeOwner(originalOwner, ctx.owner) + if (!originalOwner.isContainedIn(inlinedMethod)) Inlined(EmptyTree, Nil, result) + else result + } + /** The Inlined node representing the inlined call */ def inlined(pt: Type) = { + // Compute bindings for all parameters, appending them to bindingsBuf + computeParamBindings(inlinedMethod.info, callTypeArgs, callValueArgss) + // make sure prefix is executed if it is impure - if (!isIdempotentExpr(prefix)) registerType(meth.owner.thisType) + if (!isIdempotentExpr(inlineCallPrefix)) registerType(inlinedMethod.owner.thisType) // Register types of all leaves of inlined body so that the `paramProxy` and `thisProxy` maps are defined. rhsToInline.foreachSubTree(registerLeaf) - // The class that the this-proxy `selfSym` represents - def classOf(selfSym: Symbol) = selfSym.info.widen.classSymbol + // Compute bindings for all this-proxies, appending them to bindingsBuf + computeThisBindings() - // The total nesting depth of the class represented by `selfSym`. - def outerLevel(selfSym: Symbol): Int = classOf(selfSym).ownersIterator.length + val inlineTyper = new InlineTyper + val inlineCtx = inlineContext(call).fresh.setTyper(inlineTyper).setNewScope - // All needed this-proxies, paired-with and sorted-by nesting depth of - // the classes they represent (innermost first) - val sortedProxies = thisProxy.toList.map { - case (cls, proxy) => (outerLevel(cls), proxy.symbol) - } sortBy (-_._1) + // A tree type map to prepare the inlined body for typechecked. + // The translation maps references to `this` and parameters to + // corresponding arguments or proxies on the type and term level. It also changes + // the owner from the inlined method to the current owner. + val inliner = new TreeTypeMap( + typeMap = + new TypeMap { + def apply(t: Type) = t match { + case t: ThisType => thisProxy.getOrElse(t.cls, t) + case t: TypeRef => paramProxy.getOrElse(t, mapOver(t)) + case t: SingletonType => paramProxy.getOrElse(t, mapOver(t)) + case t => mapOver(t) + } + override def mapClassInfo(tp: ClassInfo) = mapFullClassInfo(tp) + }, + treeMap = { + case tree: This => + tree.tpe match { + case thistpe: ThisType => + thisProxy.get(thistpe.cls) match { + case Some(t) => ref(t).withPos(tree.pos) + case None => tree + } + case _ => tree + } + case tree: Ident => + paramProxy.get(tree.tpe) match { + case Some(t) if tree.isTerm && t.isSingleton => singleton(t).withPos(tree.pos) + case Some(t) if tree.isType => TypeTree(t).withPos(tree.pos) + case _ => tree + } + case tree => tree + }, + oldOwners = inlinedMethod :: Nil, + newOwners = ctx.owner :: Nil + )(inlineCtx) + + // Apply inliner to `rhsToInline`, split off any implicit bindings from result, and + // make them part of `bindingsBuf`. The expansion is then the untyped tree that remains. + val expansion = inliner.transform(rhsToInline.withPos(call.pos)) match { + case Block(implicits, tpd.UntypedSplice(expansion)) => + val prevOwners = implicits.map(_.symbol.owner).distinct + val localizer = new TreeTypeMap(oldOwners = prevOwners, newOwners = prevOwners.map(_ => ctx.owner)) + val (_, implicits1) = localizer.transformDefs(implicits) + for (idef <- implicits1) { + bindingsBuf += idef.withType(idef.symbol.typeRef).asInstanceOf[ValOrDefDef] + // Note: Substituting new symbols does not automatically lead to good prefixes + // if the previous symbol was owned by a class. That's why we need to set the type + // of `idef` explicitly. It would be nice if substituters were smarter, but + // it seems non-trivial to come up with rules that work in all cases. + inlineCtx.enter(idef.symbol) + } + expansion + case tpd.UntypedSplice(expansion) => + expansion + case expansion => + expansion + } - // Compute val-definitions for all this-proxies and append them to `bindingsBuf` - var lastSelf: Symbol = NoSymbol - var lastLevel: Int = 0 - for ((level, selfSym) <- sortedProxies) { - lazy val rhsClsSym = selfSym.info.widenDealias.classSymbol - val rhs = - if (lastSelf.exists) - ref(lastSelf).outerSelect(lastLevel - level, selfSym.info) - else if (rhsClsSym.is(Module) && rhsClsSym.isStatic) - ref(rhsClsSym.sourceModule) - else - prefix - bindingsBuf += ValDef(selfSym.asTerm, rhs) - inlining.println(i"proxy at $level: $selfSym = ${bindingsBuf.last}") - lastSelf = selfSym - lastLevel = level + trace(i"inlining $call", inlining, show = true) { + + // The normalized bindings collected in `bindingsBuf` + bindingsBuf.transform(reducer.normalizeBinding(_)(inlineCtx)) + + // Run a typing pass over the inlined tree. See InlineTyper for details. + val expansion1 = inlineTyper.typed(expansion, pt)(inlineCtx) + + if (ctx.settings.verbose.value) { + inlining.println(i"to inline = $rhsToInline") + inlining.println(i"original bindings = ${bindingsBuf.toList}%\n%") + inlining.println(i"original expansion = $expansion1") + } + + // Drop unused bindings + val matchBindings = reducer.matchBindingsBuf.toList + val (finalBindings, finalExpansion) = dropUnusedDefs(bindingsBuf.toList ++ matchBindings, expansion1) + val (finalMatchBindings, finalArgBindings) = finalBindings.partition(matchBindings.contains(_)) + + // Take care that only argument bindings go into `bindings`, since positions are + // different for bindings from arguments and bindings from body. + tpd.Inlined(call, finalArgBindings, seq(finalMatchBindings, finalExpansion)) } + } + + /** A utility object offering methods for rewriting inlined code */ + object reducer { - // The type map to apply to the inlined tree. This maps references to this-types - // and parameters to type references of their arguments or proxies. - val typeMap = new TypeMap { - def apply(t: Type) = t match { - case t: ThisType => thisProxy.getOrElse(t.cls, t) - case t: TypeRef => paramProxy.getOrElse(t, mapOver(t)) - case t: SingletonType => paramProxy.getOrElse(t, mapOver(t)) - case t => mapOver(t) + /** Additional bindings established by reducing match expressions */ + val matchBindingsBuf = new mutable.ListBuffer[MemberDef] + + /** An extractor for terms equivalent to `new C(args)`, returning the class `C`, + * a list of bindings, and the arguments `args`. Can see inside blocks and Inlined nodes and can + * follow a reference to an inline value binding to its right hand side. + * @return optionally, a triple consisting of + * - the class `C` + * - the arguments `args` + * - any bindings that wrap the instance creation + */ + private object NewInstance { + def unapply(tree: Tree)(implicit ctx: Context): Option[(Symbol, List[Tree], List[Tree])] = { + def unapplyLet(bindings: List[Tree], expr: Tree) = + unapply(expr) map { + case (cls, reduced, prefix) => (cls, reduced, bindings ::: prefix) + } + tree match { + case Apply(fn, args) => + fn match { + case Select(New(tpt), nme.CONSTRUCTOR) => + Some((tpt.tpe.classSymbol, args, Nil)) + case TypeApply(Select(New(tpt), nme.CONSTRUCTOR), _) => + Some((tpt.tpe.classSymbol, args, Nil)) + case _ => + val meth = fn.symbol + if (meth.name == nme.apply && + meth.flags.is(Synthetic) && + meth.owner.linkedClass.is(Case)) Some(meth.owner.linkedClass, args, Nil) + else None + } + case Ident(_) => + inlineBindings.get(tree.symbol).flatMap(unapply) + case Inlined(_, bindings, expansion) => + unapplyLet(bindings, expansion) + case Block(stats, expr) if isPureExpr(tree) => + unapplyLet(stats, expr) + case _ => + None + } } - override def mapClassInfo(tp: ClassInfo) = mapFullClassInfo(tp) } - // The tree map to apply to the inlined tree. This maps references to this-types - // and parameters to references of their arguments or their proxies. - def treeMap(tree: Tree) = { + /** If we are inlining a transparent method and `tree` is equivalent to `new C(args).x` + * where class `C` does not have initialization code and `x` is a parameter corresponding + * to one of the arguments `args`, the corresponding argument, prefixed by the evaluation + * of impure arguments, otherwise `tree` itself. + */ + def reduceProjection(tree: Tree)(implicit ctx: Context): Tree = { + if (ctx.debug) inlining.println(i"try reduce projection $tree") tree match { - case _: This => - tree.tpe match { - case thistpe: ThisType => - thisProxy.get(thistpe.cls) match { - case Some(t) => ref(t).withPos(tree.pos) - case None => tree + case Select(NewInstance(cls, args, prefix), field) if cls.isNoInitsClass => + def matches(param: Symbol, selection: Symbol): Boolean = + param == selection || { + selection.name match { + case InlineAccessorName(underlying) => + param.name == underlying && selection.info.isInstanceOf[ExprType] + case _ => + false + } } - case _ => tree - } - case _: Ident => - paramProxy.get(tree.tpe) match { - case Some(t) if tree.isTerm && t.isSingleton => singleton(t).withPos(tree.pos) - case Some(t) if tree.isType => TypeTree(t).withPos(tree.pos) + val idx = cls.asClass.paramAccessors.indexWhere(matches(_, tree.symbol)) + if (idx >= 0 && idx < args.length) { + def collectImpure(from: Int, end: Int) = + (from until end).filterNot(i => isPureExpr(args(i))).toList.map(args) + val leading = collectImpure(0, idx) + val trailing = collectImpure(idx + 1, args.length) + val arg = args(idx) + val argInPlace = + if (trailing.isEmpty) arg + else letBindUnless(TreeInfo.Pure, arg)(seq(trailing, _)) + seq(prefix, seq(leading, argInPlace)) + .reporting(res => i"projecting $tree -> $res", inlining) + } + else tree + case _ => tree + } + } + + /** If this is a value binding: + * - reduce its rhs if it is a projection and adjust its type accordingly, + * - record symbol -> rhs in the InlineBindings context propery. + */ + def normalizeBinding(binding: MemberDef)(implicit ctx: Context) = { + val binding1 = binding match { + case binding: ValDef => + val rhs1 = reduceProjection(binding.rhs) + inlineBindings(ctx).put(binding.symbol, rhs1) + if (rhs1 `eq` binding.rhs) binding + else { + binding.symbol.info = rhs1.tpe + cpy.ValDef(binding)(tpt = TypeTree(rhs1.tpe), rhs = rhs1) + } + case _ => + binding + } + binding1.withPos(call.pos) + } + + /** An extractor for references to inlineable arguments. These are : + * - by-value arguments marked with `inline` + * - all by-name arguments + */ + private object InlineableArg { + lazy val paramProxies = paramProxy.values.toSet + def unapply(tree: Trees.Ident[_])(implicit ctx: Context): Option[Tree] = { + def search(buf: mutable.ListBuffer[MemberDef]) = buf.find(_.name == tree.name) + if (paramProxies.contains(tree.typeOpt)) + search(bindingsBuf).orElse(search(matchBindingsBuf)) match { + case Some(vdef: ValDef) if vdef.symbol.is(Transparent) => + Some(integrate(vdef.rhs, vdef.symbol)) + case Some(ddef: DefDef) => + Some(integrate(ddef.rhs, ddef.symbol)) + case _ => None + } + else None + } + } + + object ConstantValue { + def unapply(tree: Tree)(implicit ctx: Context) = tree.tpe.widenTermRefExpr match { + case ConstantType(Constant(x)) => Some(x) + case _ => None + } + } + + def tryInline(tree: tpd.Tree)(implicit ctx: Context) = tree match { + case InlineableArg(rhs) => + inlining.println(i"inline arg $tree -> $rhs") + rhs + case _ => + EmptyTree + } + + /** Rewrite an application + * + * ((x1, ..., xn) => b)(e1, ..., en) + * + * to + * + * val/def x1 = e1; ...; val/def xn = en; b + * + * where `def` is used for call-by-name parameters. However, we shortcut any NoPrefix + * refs among the ei's directly without creating an intermediate binding. + */ + def betaReduce(tree: Tree)(implicit ctx: Context) = tree match { + case Apply(Select(cl @ closureDef(ddef), nme.apply), args) if defn.isFunctionType(cl.tpe) => + ddef.tpe.widen match { + case mt: MethodType if ddef.vparamss.head.length == args.length => + val bindingsBuf = new mutable.ListBuffer[MemberDef] + val argSyms = (mt.paramNames, mt.paramInfos, args).zipped.map { (name, paramtp, arg) => + arg.tpe.dealias match { + case ref @ TermRef(NoPrefix, _) => ref.symbol + case _ => paramBindingDef(name, paramtp, arg, bindingsBuf).symbol + } + } + val expander = new TreeTypeMap( + oldOwners = ddef.symbol :: Nil, + newOwners = ctx.owner :: Nil, + substFrom = ddef.vparamss.head.map(_.symbol), + substTo = argSyms) + Block(bindingsBuf.toList, expander.transform(ddef.rhs)) case _ => tree } case _ => tree - }} - - val inlineCtx = inlineContext(call) - // The complete translation maps references to `this` and parameters to - // corresponding arguments or proxies on the type and term level. It also changes - // the owner from the inlined method to the current owner. - val inliner = new TreeTypeMap(typeMap, treeMap, meth :: Nil, ctx.owner :: Nil)(inlineCtx) + } - val expansion = inliner(rhsToInline.withPos(call.pos)) - trace(i"inlining $call\n, BINDINGS =\n${bindingsBuf.toList}%\n%\nEXPANSION =\n$expansion", inlining, show = true) { + /** The result type of reducing a match. It consists, optionally of a list of bindings + * for the pattern-bound variables and the RHS of the selected case. + * Returns `None` if not case was selected. + */ + type MatchRedux = Option[(List[MemberDef], untpd.Tree)] + + /** Reduce a toplevel match of a transparent function + * @param scrutinee the scrutinee expression, assumed to be pure + * @param scrutType its fully defined type + * @param cases All cases of the match + * @param typer The current inline typer + * @return optionally, if match can be reduced to a matching case: A pair of + * bindings for all pattern-bound variables and the untyped RHS of the case. + */ + def reduceTopLevelMatch(scrutinee: Tree, scrutType: Type, cases: List[untpd.CaseDef], typer: Typer)(implicit ctx: Context): MatchRedux = { - // The final expansion runs a typing pass over the inlined tree. See InlineTyper for details. - val expansion1 = InlineTyper.typed(expansion, pt)(inlineCtx) + val gadtSyms = typer.gadtSyms(scrutType) - /** All bindings in `bindingsBuf` except bindings of inlineable closures */ - val bindings = bindingsBuf.toList.map(_.withPos(call.pos)) + /** Try to match pattern `pat` against scrutinee reference `scrut`. If succesful add + * bindings for variables bound in this pattern to `bindingsBuf`. + */ + def reducePattern(bindingsBuf: mutable.ListBuffer[MemberDef], scrut: TermRef, pat: Tree): Boolean = { + val isImplicit = scrut.info == defn.ImplicitScrutineeTypeRef + + def newBinding(name: TermName, flags: FlagSet, rhs: Tree): Symbol = { + val info = if (flags `is` Implicit) rhs.tpe.widen else rhs.tpe.widenTermRefExpr + val sym = newSym(name, flags, info).asTerm + bindingsBuf += ValDef(sym, constToLiteral(rhs)) + sym + } - inlining.println(i"original bindings = $bindings%\n%") - inlining.println(i"original expansion = $expansion1") + def searchImplicit(name: TermName, tpt: Tree) = { + val evidence = typer.inferImplicitArg(tpt.tpe, tpt.pos) + evidence.tpe match { + case fail: Implicits.AmbiguousImplicits => + ctx.error(typer.missingArgMsg(evidence, tpt.tpe, ""), tpt.pos) + true // hard error: return true to stop implicit search here + case fail: Implicits.SearchFailureType => + false + case _ => + if (name != nme.WILDCARD) newBinding(name, Implicit, evidence) + true + } + } - val (finalBindings, finalExpansion) = dropUnusedDefs(bindings, expansion1) + pat match { + case Typed(pat1, tpt) => + if (isImplicit) searchImplicit(nme.WILDCARD, tpt) + else scrut <:< tpt.tpe && reducePattern(bindingsBuf, scrut, pat1) + case pat @ Bind(name: TermName, Typed(_, tpt)) if isImplicit => + searchImplicit(name, tpt) + case pat @ Bind(name: TermName, body) => + reducePattern(bindingsBuf, scrut, body) && { + if (name != nme.WILDCARD) newBinding(name, EmptyFlags, ref(scrut)) + true + } + case Ident(nme.WILDCARD) => + true + case pat: Literal => + scrut.widenTermRefExpr =:= pat.tpe + case pat: RefTree => + scrut =:= pat.tpe || + scrut.widen.classSymbol.is(Module) && scrut.widen =:= pat.tpe.widen && { + scrut.prefix match { + case _: SingletonType | NoPrefix => true + case _ => false + } + } + case UnApply(unapp, _, pats) => + unapp.tpe.widen match { + case mt: MethodType if mt.paramInfos.length == 1 => + val paramType = mt.paramInfos.head + val paramCls = paramType.classSymbol + paramCls.is(Case) && unapp.symbol.is(Synthetic) && scrut <:< paramType && { + val caseAccessors = + if (paramCls.is(Scala2x)) paramCls.caseAccessors.filter(_.is(Method)) + else paramCls.asClass.paramAccessors + var subOK = caseAccessors.length == pats.length + for ((pat, accessor) <- (pats, caseAccessors).zipped) + subOK = subOK && { + val rhs = constToLiteral(reduceProjection(ref(scrut).select(accessor).ensureApplied)) + val elem = newBinding(TransparentBinderName.fresh(), Synthetic, rhs) + reducePattern(bindingsBuf, elem.termRef, pat) + } + subOK + } + case _ => + false + } + case _ => false + } + } - tpd.Inlined(call, finalBindings, finalExpansion) - } - } + /** The initial scrutinee binding: `val $scrutineeN = ` */ + val scrutineeSym = newSym(TransparentScrutineeName.fresh(), Synthetic, scrutType).asTerm + val scrutineeBinding = normalizeBinding(ValDef(scrutineeSym, scrutinee)) - /** An extractor for references to inlineable arguments. These are : - * - by-value arguments marked with `inline` - * - all by-name arguments - */ - private object InlineableArg { - lazy val paramProxies = paramProxy.values.toSet - def unapply(tree: Ident)(implicit ctx: Context): Option[Tree] = - if (paramProxies.contains(tree.tpe)) - bindingsBuf.find(_.name == tree.name) match { - case Some(vdef: ValDef) if vdef.symbol.is(Inline) => - Some(vdef.rhs.changeOwner(vdef.symbol, ctx.owner)) - case Some(ddef: DefDef) => - Some(ddef.rhs.changeOwner(ddef.symbol, ctx.owner)) - case _ => None + def reduceCase(cdef: untpd.CaseDef): MatchRedux = { + def guardOK = cdef.guard.isEmpty || { + typer.typed(cdef.guard, defn.BooleanType) match { + case ConstantValue(true) => true + case _ => false + } } - else None + val caseBindingsBuf = new mutable.ListBuffer[MemberDef]() + if (scrutType != defn.ImplicitScrutineeTypeRef) caseBindingsBuf += scrutineeBinding + val pat1 = typer.typedPattern(cdef.pat, scrutType)(typer.gadtContext(gadtSyms)) + if (reducePattern(caseBindingsBuf, scrutineeSym.termRef, pat1) && guardOK) + Some((caseBindingsBuf.toList, cdef.body)) + else + None + } + + def recur(cases: List[untpd.CaseDef]): MatchRedux = cases match { + case Nil => None + case cdef :: cases1 => reduceCase(cdef) `orElse` recur(cases1) + } + + recur(cases) + } } - /** A typer for inlined code. Its purpose is: + /** A typer for inlined bodies of transparent methods. Beyond standard typing + * an inline typer performs the following functions: + * * 1. Implement constant folding over inlined code * 2. Selectively expand ifs with constant conditions * 3. Inline arguments that are by-name closures * 4. Make sure inlined code is type-correct. * 5. Make sure that the tree's typing is idempotent (so that future -Ycheck passes succeed) */ - private object InlineTyper extends ReTyper { + class InlineTyper extends Typer { + import reducer._ override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = { tpe match { - case tpe: NamedType if !tpe.symbol.isAccessibleFrom(tpe.prefix, superAccess) => + case tpe: NamedType if tpe.symbol.exists && !tpe.symbol.isAccessibleFrom(tpe.prefix, superAccess) => tpe.info match { case TypeAlias(alias) => return ensureAccessible(alias, superAccess, pos) case info: ConstantType if tpe.symbol.isStable => return info @@ -556,79 +712,61 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { super.ensureAccessible(tpe, superAccess, pos) } - override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context) = - tree.asInstanceOf[tpd.Tree] match { - case InlineableArg(rhs) => inlining.println(i"inline arg $tree -> $rhs"); rhs - case _ => super.typedIdent(tree, pt) - } + override def typedTypedSplice(tree: untpd.TypedSplice)(implicit ctx: Context): Tree = + reduceProjection(tryInline(tree.splice) `orElse` super.typedTypedSplice(tree)) - override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { - assert(tree.hasType, tree) - val qual1 = typed(tree.qualifier, selectionProto(tree.name, pt, this)) - val res = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt) - ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.pos) - res - } + override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context) = + constToLiteral(reduceProjection(super.typedSelect(tree, pt))) - override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = { - val cond1 = typed(tree.cond, defn.BooleanType) - cond1.tpe.widenTermRefExpr match { - case ConstantType(Constant(condVal: Boolean)) => - val selected = typed(if (condVal) tree.thenp else tree.elsep, pt) + override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = + typed(tree.cond, defn.BooleanType) match { + case cond1 @ ConstantValue(b: Boolean) => + val selected0 = if (b) tree.thenp else tree.elsep + val selected = if (selected0.isEmpty) tpd.Literal(Constant(())) else typed(selected0, pt) if (isIdempotentExpr(cond1)) selected else Block(cond1 :: Nil, selected) - case _ => + case cond1 => val if1 = untpd.cpy.If(tree)(cond = untpd.TypedSplice(cond1)) super.typedIf(if1, pt) } - } - override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context) = { - - /** Rewrite an application - * - * ((x1, ..., sn) => b)(e1, ..., en) - * - * to - * - * val/def x1 = e1; ...; val/def xn = en; b - * - * where `def` is used for call-by-name parameters. However, we shortcut any NoPrefix - * refs among the ei's directly without creating an intermediate binding. - */ - def betaReduce(tree: Tree) = tree match { - case Apply(Select(cl @ closureDef(ddef), nme.apply), args) if defn.isFunctionType(cl.tpe) => - ddef.tpe.widen match { - case mt: MethodType if ddef.vparamss.head.length == args.length => - val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] - val argSyms = (mt.paramNames, mt.paramInfos, args).zipped.map { (name, paramtp, arg) => - arg.tpe.dealias match { - case ref @ TermRef(NoPrefix, _) => ref.symbol - case _ => paramBindingDef(name, paramtp, arg, bindingsBuf).symbol - } + override def typedMatchFinish(tree: untpd.Match, sel: Tree, selType: Type, pt: Type)(implicit ctx: Context) = + tree.getAttachment(PrepareTransparent.TopLevelMatch) match { + case Some(_) if !ctx.owner.isTransparentMethod => // don't reduce match of nested transparent yet + reduceTopLevelMatch(sel, sel.tpe, tree.cases, this) match { + case Some((caseBindings, rhs)) => + var rhsCtx = ctx.fresh.setNewScope + for (binding <- caseBindings) { + matchBindingsBuf += binding + rhsCtx.enter(binding.symbol) } - val expander = new TreeTypeMap( - oldOwners = ddef.symbol :: Nil, - newOwners = ctx.owner :: Nil, - substFrom = ddef.vparamss.head.map(_.symbol), - substTo = argSyms) - Block(bindingsBuf.toList, expander.transform(ddef.rhs)) - case _ => tree + typedExpr(rhs, pt)(rhsCtx) + case None => + def guardStr(guard: untpd.Tree) = if (guard.isEmpty) "" else i" if $guard" + def patStr(cdef: untpd.CaseDef) = i"case ${cdef.pat}${guardStr(cdef.guard)}" + errorTree(tree, em"""cannot reduce top-level match of transparent function with + | scrutinee: $sel : ${sel.tpe} + | patterns : ${tree.cases.map(patStr).mkString("\n ")} + | + | Hint: if you do not expect the match to be reduced, put it in a locally { ... } block.""") } - case _ => tree + case _ => + super.typedMatchFinish(tree, sel, selType, pt) } - betaReduce(super.typedApply(tree, pt)) - } + override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context) = + constToLiteral(betaReduce(super.typedApply(tree, pt))) + + override def newLikeThis: Typer = new InlineTyper } /** Drop any side-effect-free bindings that are unused in expansion or other reachable bindings. * Inline def bindings that are used only once. */ - def dropUnusedDefs(bindings: List[ValOrDefDef], tree: Tree)(implicit ctx: Context): (List[ValOrDefDef], Tree) = { + def dropUnusedDefs(bindings: List[MemberDef], tree: Tree)(implicit ctx: Context): (List[MemberDef], Tree) = { val refCount = newMutableSymbolMap[Int] - val bindingOfSym = newMutableSymbolMap[ValOrDefDef] - def isInlineable(binding: ValOrDefDef) = binding match { + val bindingOfSym = newMutableSymbolMap[MemberDef] + def isInlineable(binding: MemberDef) = binding match { case DefDef(_, Nil, Nil, _, _) => true case vdef @ ValDef(_, _, _) => isPureExpr(vdef.rhs) case _ => false @@ -639,19 +777,13 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } val countRefs = new TreeTraverser { override def traverse(t: Tree)(implicit ctx: Context) = { + def updateRefCount(sym: Symbol, inc: Int) = + for (x <- refCount.get(sym)) refCount(sym) = x + inc t match { - case t: RefTree => - refCount.get(t.symbol) match { - case Some(x) => refCount(t.symbol) = x + 1 - case none => - } + case t: RefTree => updateRefCount(t.symbol, 1) case _: New | _: TypeTree => - t.tpe.foreachPart { - case ref: TermRef => - refCount.get(ref.symbol) match { - case Some(x) => refCount(ref.symbol) = x + 2 - case none => - } + t.typeOpt.foreachPart { + case ref: TermRef => updateRefCount(ref.symbol, 2) // can't be inlined, so make sure refCount is at least 2 case _ => } case _ => @@ -660,23 +792,27 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } } countRefs.traverse(tree) - for (binding <- bindings) countRefs.traverse(binding.rhs) + for (binding <- bindings) countRefs.traverse(binding) val inlineBindings = new TreeMap { - override def transform(t: Tree)(implicit ctx: Context) = - super.transform { - t match { - case t: RefTree => - val sym = t.symbol - refCount.get(sym) match { - case Some(1) if sym.is(Method) => - bindingOfSym(sym).rhs.changeOwner(sym, ctx.owner) - case none => t + override def transform(t: Tree)(implicit ctx: Context) = t match { + case t: RefTree => + val sym = t.symbol + val t1 = refCount.get(sym) match { + case Some(1) => + bindingOfSym(sym) match { + case binding: ValOrDefDef => integrate(binding.rhs, sym) } - case _ => t + case none => t } - } + super.transform(t1) + case t: Apply => + val t1 = super.transform(t) + if (t1 `eq` t) t else reducer.betaReduce(t1) + case _ => + super.transform(t) } - def retain(binding: ValOrDefDef) = refCount.get(binding.symbol) match { + } + def retain(binding: MemberDef) = refCount.get(binding.symbol) match { case Some(x) => x > 1 || x == 1 && !binding.symbol.is(Method) case none => true } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index a052786f8b61..bca80fad2687 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -754,15 +754,16 @@ class Namer { typer: Typer => val ann = Annotation.deferred(cls, implicit ctx => typedAnnotation(annotTree)) sym.addAnnotation(ann) if (cls == defn.ForceInlineAnnot && sym.is(Method, butNot = Accessor)) - sym.setFlag(Inline) + sym.setFlag(Transparent) } case _ => } private def addInlineInfo(sym: Symbol) = original match { - case original: untpd.DefDef if sym.isInlineableMethod => - Inliner.registerInlineInfo( + case original: untpd.DefDef if sym.isTransparentInlineable => + PrepareTransparent.registerInlineInfo( sym, + original.rhs, implicit ctx => typedAheadExpr(original).asInstanceOf[tpd.DefDef].rhs )(localContext(sym)) case _ => @@ -1060,7 +1061,7 @@ class Namer { typer: Typer => ctx.defContext(sym).denotNamed(original) def paramProto(paramss: List[List[Type]], idx: Int): Type = paramss match { case params :: paramss1 => - if (idx < params.length) wildApprox(params(idx), null, Set.empty) + if (idx < params.length) wildApprox(params(idx)) else paramProto(paramss1, idx - params.length) case nil => WildcardType @@ -1074,7 +1075,7 @@ class Namer { typer: Typer => // println(s"final inherited for $sym: ${inherited.toString}") !!! // println(s"owner = ${sym.owner}, decls = ${sym.owner.info.decls.show}") - def isInline = sym.is(FinalOrInlineOrTransparent, butNot = Method | Mutable) + def isInline = sym.is(FinalOrTransparent, butNot = Method | Mutable) // Widen rhs type and eliminate `|' but keep ConstantTypes if // definition is inline (i.e. final in Scala2) and keep module singleton types @@ -1089,7 +1090,11 @@ class Namer { typer: Typer => // it would be erased to BoxedUnit. def dealiasIfUnit(tp: Type) = if (tp.isRef(defn.UnitClass)) defn.UnitType else tp - val rhsCtx = ctx.addMode(Mode.InferringReturnType) + var rhsCtx = ctx.addMode(Mode.InferringReturnType) + if (sym.isTransparentMethod) { + rhsCtx = rhsCtx.addMode(Mode.TransparentBody) + PrepareTransparent.markTopLevelMatches(sym, mdef.rhs) + } def rhsType = typedAheadExpr(mdef.rhs, inherited orElse rhsProto)(rhsCtx).tpe // Approximate a type `tp` with a type that does not contain skolem types. @@ -1109,7 +1114,7 @@ class Namer { typer: Typer => if (sym.is(Final, butNot = Method)) { val tp = lhsType if (tp.isInstanceOf[ConstantType]) - tp // keep constant types that fill in for a non-constant (to be revised when inline has landed). + tp // keep constant types that fill in for a non-constant (to be revised when transparent has landed). else inherited } else inherited @@ -1190,6 +1195,15 @@ class Namer { typer: Typer => instantiateDependent(restpe, typeParams, termParamss) ctx.methodType(tparams map symbolOfTree, termParamss, restpe, isJava = ddef.mods is JavaDefined) } + if (sym.is(Transparent) && + sym.unforcedAnnotation(defn.ForceInlineAnnot).isEmpty) + // Need to keep @forceInline annotated methods around to get to parity with Scala. + // This is necessary at least until we have full bootstrap. Right now + // dotty-bootstrapped involves running the Dotty compiler compiled with Scala 2 with + // a Dotty runtime library compiled with Dotty. If we erase @forceInline annotated + // methods, this means that the support methods in dotty.runtime.LazyVals vanish. + // But they are needed for running the lazy val implementations in the Scala-2 compiled compiler. + sym.setFlag(Erased) if (isConstructor) { // set result type tree to unit, but take the current class as result type of the symbol typedAheadType(ddef.tpt, defn.UnitType) diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala new file mode 100644 index 000000000000..927ce6f4f053 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala @@ -0,0 +1,429 @@ +package dotty.tools +package dotc +package typer + +import dotty.tools.dotc.ast.Trees.NamedArg +import dotty.tools.dotc.ast.{Trees, untpd, tpd, TreeTypeMap} +import Trees._ +import core._ +import Flags._ +import Symbols._ +import Types._ +import Decorators._ +import Constants._ +import StdNames.nme +import Contexts.Context +import Names.{Name, TermName, EmptyTermName} +import NameOps._ +import NameKinds.{ClassifiedNameKind, InlineAccessorName, UniqueInlineName} +import ProtoTypes.selectionProto +import SymDenotations.SymDenotation +import Annotations._ +import transform.{ExplicitOuter, AccessProxies} +import Inferencing.fullyDefinedType +import config.Printers.inlining +import ErrorReporting.errorTree +import collection.mutable +import transform.TypeUtils._ +import reporting.trace +import util.Positions.Position +import util.Property +import ast.TreeInfo + +object PrepareTransparent { + import tpd._ + + /** Marks an implicit reference found in the context (as opposed to the implicit scope) + * from an inlineable body. Such references will be carried along with the body to + * the expansion site. + */ + private val ContextualImplicit = new Property.StickyKey[Unit] + + /** An attachment labeling a toplevel match node of a transparent function */ + val TopLevelMatch = new Property.StickyKey[Unit] + + def markContextualImplicit(tree: Tree)(implicit ctx: Context): Unit = + if (!defn.ScalaPredefModule.moduleClass.derivesFrom(tree.symbol.maybeOwner)) + methPart(tree).putAttachment(ContextualImplicit, ()) + + def markTopLevelMatches(meth: Symbol, tree: untpd.Tree)(implicit ctx: Context): Unit = tree match { + case tree: untpd.Match => + tree.putAttachment(TopLevelMatch, ()) + tree.cases.foreach(markTopLevelMatches(meth, _)) + case tree: untpd.Block => + markTopLevelMatches(meth, tree.expr) + case _ => + } + + class InlineAccessors extends AccessProxies { + + /** If an inline accessor name wraps a unique inline name, this is taken as indication + * that the inline accessor takes its receiver as first parameter. Such accessors + * are created by MakeInlineablePassing. + */ + override def passReceiverAsArg(name: Name)(implicit ctx: Context) = name match { + case InlineAccessorName(UniqueInlineName(_, _)) => true + case _ => false + } + + /** A tree map which inserts accessors for non-public term members accessed from inlined code. + */ + abstract class MakeInlineableMap(val inlineSym: Symbol) extends TreeMap with Insert { + def accessorNameKind = InlineAccessorName + + /** A definition needs an accessor if it is private, protected, or qualified private + * and it is not part of the tree that gets inlined. The latter test is implemented + * by excluding all symbols properly contained in the inlined method. + * + * Constant vals don't need accessors since they are inlined in FirstTransform. + */ + def needsAccessor(sym: Symbol)(implicit ctx: Context) = + sym.isTerm && + (sym.is(AccessFlags) || sym.privateWithin.exists) && + !sym.isContainedIn(inlineSym) && + !(sym.isStable && sym.info.widenTermRefExpr.isInstanceOf[ConstantType]) + + def preTransform(tree: Tree)(implicit ctx: Context): Tree + + def postTransform(tree: Tree)(implicit ctx: Context) = tree match { + case Assign(lhs, rhs) if lhs.symbol.name.is(InlineAccessorName) => + cpy.Apply(tree)(useSetter(lhs), rhs :: Nil) + case _ => + tree + } + + override def transform(tree: Tree)(implicit ctx: Context): Tree = + postTransform(super.transform(preTransform(tree))) + } + + /** Direct approach: place the accessor with the accessed symbol. This has the + * advantage that we can re-use the receiver as is. But it is only + * possible if the receiver is essentially this or an outer this, which is indicated + * by the test that we can find a host for the accessor. + */ + class MakeInlineableDirect(inlineSym: Symbol) extends MakeInlineableMap(inlineSym) { + def preTransform(tree: Tree)(implicit ctx: Context): Tree = tree match { + case tree: RefTree if needsAccessor(tree.symbol) => + if (tree.symbol.isConstructor) { + ctx.error("Implementation restriction: cannot use private constructors in transparent methods", tree.pos) + tree // TODO: create a proper accessor for the private constructor + } + else useAccessor(tree) + case _ => + tree + } + override def ifNoHost(reference: RefTree)(implicit ctx: Context): Tree = reference + } + + /** Fallback approach if the direct approach does not work: Place the accessor method + * in the same class as the inlined method, and let it take the receiver as parameter. + * This is tricky, since we have to find a suitable type for the parameter, which might + * require additional type parameters for the inline accessor. An example is in the + * `TestPassing` class in test `run/inline/inlines_1`: + * + * class C[T](x: T) { + * private[inlines] def next[U](y: U): (T, U) = (x, y) + * } + * class TestPassing { + * transparent def foo[A](x: A): (A, Int) = { + * val c = new C[A](x) + * c.next(1) + * } + * transparent def bar[A](x: A): (A, String) = { + * val c = new C[A](x) + * c.next("") + * } + * + * `C` could be compiled separately, so we cannot place the inline accessor in it. + * Instead, the inline accessor goes into `TestPassing` and takes the actual receiver + * type as argument: + * + * def inline$next$i1[A, U](x$0: C[A])(y: U): (A, U) = + * x$0.next[U](y) + * + * Since different calls might have different receiver types, we need to generate one + * such accessor per call, so they need to have unique names. + */ + class MakeInlineablePassing(inlineSym: Symbol) extends MakeInlineableMap(inlineSym) { + + def preTransform(tree: Tree)(implicit ctx: Context): Tree = tree match { + case _: Apply | _: TypeApply | _: RefTree + if needsAccessor(tree.symbol) && tree.isTerm && !tree.symbol.isConstructor => + val (refPart, targs, argss) = decomposeCall(tree) + val qual = qualifier(refPart) + inlining.println(i"adding receiver passing inline accessor for $tree/$refPart -> (${qual.tpe}, $refPart: ${refPart.getClass}, [$targs%, %], ($argss%, %))") + + // Need to dealias in order to cagtch all possible references to abstracted over types in + // substitutions + val dealiasMap = new TypeMap { + def apply(t: Type) = mapOver(t.dealias) + } + val qualType = dealiasMap(qual.tpe.widen) + + // The types that are local to the inlined method, and that therefore have + // to be abstracted out in the accessor, which is external to the inlined method + val localRefs = qualType.namedPartsWith(ref => + ref.isType && ref.symbol.isContainedIn(inlineSym)).toList + + // Add qualifier type as leading method argument to argument `tp` + def addQualType(tp: Type): Type = tp match { + case tp: PolyType => tp.derivedLambdaType(tp.paramNames, tp.paramInfos, addQualType(tp.resultType)) + case tp: ExprType => addQualType(tp.resultType) + case tp => MethodType(qualType.simplified :: Nil, tp) + } + + // Abstract accessed type over local refs + def abstractQualType(mtpe: Type): Type = + if (localRefs.isEmpty) mtpe + else PolyType.fromParams(localRefs.map(_.symbol.asType), mtpe) + .asInstanceOf[PolyType].flatten + + val accessed = refPart.symbol.asTerm + val accessedType = refPart.tpe.widen + val accessor = accessorSymbol( + owner = inlineSym.owner, + accessorName = InlineAccessorName(UniqueInlineName.fresh(accessed.name)), + accessorInfo = abstractQualType(addQualType(dealiasMap(accessedType))), + accessed = accessed) + + ref(accessor) + .appliedToTypeTrees(localRefs.map(TypeTree(_)) ++ targs) + .appliedToArgss((qual :: Nil) :: argss) + .withPos(tree.pos) + + // TODO: Handle references to non-public types. + // This is quite tricky, as such types can appear anywhere, including as parts + // of types of other things. For the moment we do nothing and complain + // at the implicit expansion site if there's a reference to an inaccessible type. + // Draft code (incomplete): + // + // val accessor = accessorSymbol(tree, TypeAlias(tree.tpe)).asType + // myAccessors += TypeDef(accessor).withPos(tree.pos.focus) + // ref(accessor).withPos(tree.pos) + // + case _ => tree + } + } + + /** Adds accessors for all non-public term members accessed + * from `tree`. Non-public type members are currently left as they are. + * This means that references to a private type will lead to typing failures + * on the code when it is inlined. Less than ideal, but hard to do better (see below). + * + * @return If there are accessors generated, a thicket consisting of the rewritten `tree` + * and all accessors, otherwise the original tree. + */ + def makeInlineable(tree: Tree)(implicit ctx: Context) = { + val inlineSym = ctx.owner + if (inlineSym.owner.isTerm) + // Transparent methods in local scopes can only be called in the scope they are defined, + // so no accessors are needed for them. + tree + else + new MakeInlineablePassing(inlineSym).transform( + new MakeInlineableDirect(inlineSym).transform(tree)) + } + } + + def isLocalOrParam(sym: Symbol, inlineMethod: Symbol)(implicit ctx: Context) = + sym.isContainedIn(inlineMethod) && sym != inlineMethod + + def isLocal(sym: Symbol, inlineMethod: Symbol)(implicit ctx: Context) = + isLocalOrParam(sym, inlineMethod) && !(sym.is(Param) && sym.owner == inlineMethod) + + /** Register inline info for given transparent method `sym`. + * + * @param sym The symbol denotatioon of the transparent method for which info is registered + * @param treeExpr A function that computes the tree to be inlined, given a context + * This tree may still refer to non-public members. + * @param ctx The context to use for evaluating `treeExpr`. It needs + * to have the inlined method as owner. + */ + def registerInlineInfo( + inlined: Symbol, originalBody: untpd.Tree, treeExpr: Context => Tree)(implicit ctx: Context): Unit = { + inlined.unforcedAnnotation(defn.BodyAnnot) match { + case Some(ann: ConcreteBodyAnnotation) => + case Some(ann: LazyBodyAnnotation) if ann.isEvaluated => + case _ => + if (!ctx.isAfterTyper) { + val inlineCtx = ctx + inlined.updateAnnotation(LazyBodyAnnotation { _ => + implicit val ctx = inlineCtx + val rawBody = treeExpr(ctx) + val typedBody = + if (ctx.reporter.hasErrors) rawBody + else ctx.compilationUnit.inlineAccessors.makeInlineable(rawBody) + val inlineableBody = addReferences(inlined, originalBody, typedBody) + inlining.println(i"Body to inline for $inlined: $inlineableBody") + inlineableBody + }) + } + } + } + + /** Tweak untyped tree `original` so that all external references are typed + * and it reflects the changes in the corresponding typed tree `typed` that + * make `typed` inlineable. Concretely: + * + * - all external references via identifiers or this-references are converted + * to typed splices, + * - if X gets an inline accessor in `typed`, references to X in `original` + * are converted to the inline accessor name. + */ + private def addReferences(inlineMethod: Symbol, + original: untpd.Tree, typed: tpd.Tree)(implicit ctx: Context): tpd.Tree = { + + // Maps from positions to external reference types and inline selector names. + object referenced extends TreeTraverser { + val typeAtPos = mutable.Map[Position, Type]() + val accessorAtPos = mutable.Map[Position, Symbol]() + val implicitRefTypes = mutable.Set[Type]() + val implicitRefs = new mutable.ListBuffer[Tree] + + def registerIfContextualImplicit(tree: Tree) = tree match { + case tree: RefTree + if tree.removeAttachment(ContextualImplicit).isDefined && + tree.symbol.exists && + !isLocalOrParam(tree.symbol, inlineMethod) && + !implicitRefTypes.contains(tree.tpe) => + if (tree.existsSubTree(t => isLocal(tree.symbol, inlineMethod))) + ctx.warning("implicit reference $tree is dropped at inline site because it refers to local symbol(s)", tree.pos) + else { + implicitRefTypes += tree.tpe + implicitRefs += tree + } + case _ => + } + + def registerAccessor(tree: Tree) = { + inlining.println(i"accessor: $tree at ${tree.pos}") + accessorAtPos(tree.pos.toSynthetic) = tree.symbol + // Note: It's possible that during traversals several accessors are stored under the same + // position. This could happen for instance for implicit conersions added around a tree. + // or for a setter containing a getter in an op-assignment node. + // In general, it's always the innermost tree that holds the relevant symbol. The traversal + // order guarantees that the innermost tree's symbol is stored last, and thereby replaces all previously + // stored symbols. + } + + def traverse(tree: Tree)(implicit ctx: Context): Unit = { + val sym = tree.symbol + tree match { + case Ident(nme.WILDCARD) => + case _: Ident | _: This => + //println(i"leaf: $tree at ${tree.pos}") + if (sym.exists && !isLocal(sym, inlineMethod)) { + if (ctx.debug) inlining.println(i"type at $tree @ ${tree.pos.toSynthetic} = ${tree.tpe}") + tree.tpe match { + case tp: NamedType if tp.prefix.member(sym.name).isOverloaded => + // refer to prefix instead of to ident directly, so that overloading can be resolved + // again at expansion site + typeAtPos(tree.pos.startPos) = tp.prefix + case _ => + typeAtPos(tree.pos.toSynthetic) = tree.tpe + } + // Note: It's possible that during traversals several types are stored under the same + // position. This could happen for instance for implicit conersions added around a tree. + // In general, it's always the innermost tree that holds the relevant type. The traversal + // order guarantees that the innermost tree's type is stored last, and thereby replaces all previously + // stored types. + } + case _: Select => + sym.name match { + case InlineAccessorName(UniqueInlineName(_, _)) => return // was already recorded in Apply + case InlineAccessorName(_) => registerAccessor(tree) + case _ => + } + case Apply(_: RefTree | _: TypeApply, receiver :: Nil) => + sym.name match { + case InlineAccessorName(UniqueInlineName(_, _)) => registerAccessor(tree) + case _ => + } + case _ => + } + registerIfContextualImplicit(tree) + traverseChildren(tree) + } + } + referenced.traverse(typed) + + // The untyped tree transform that applies the tweaks + object addRefs extends untpd.UntypedTreeMap { + override def transform(tree: untpd.Tree)(implicit ctx: Context): untpd.Tree = { + + def adjustLeaf(tree: untpd.Tree): untpd.Tree = referenced.typeAtPos.get(tree.pos.toSynthetic) match { + case Some(tpe) => untpd.TypedSplice(tree.withType(tpe)) + case none => tree + } + + def adjustForAccessor(ref: untpd.RefTree) = + referenced.accessorAtPos.get(ref.pos.toSynthetic) match { + case Some(acc) => + def accessorRef = untpd.TypedSplice(tpd.ref(acc)) + acc.name match { + case InlineAccessorName(UniqueInlineName(_, _)) => + // In this case we are seeing a pair like this: + // untyped typed + // t.x inline$x(t) + // Drop the selection, since it is part of the accessor + val Select(qual, _) = ref + untpd.Apply(accessorRef, qual :: Nil) + case _ => + accessorRef + } + case none => ref + } + + def adjustQualifier(tree: untpd.Tree): untpd.Tree = tree match { + case tree @ Ident(name1) => + referenced.typeAtPos.get(tree.pos.startPos) match { + case Some(tp: ThisType) => + val qual = untpd.TypedSplice(This(tp.cls).withPos(tree.pos.startPos)) + cpy.Select(tree)(qual, name1) + case none => + tree + } + case tree => tree + } + + def isAccessorLHS(lhs: untpd.Tree): Boolean = lhs match { + case lhs: untpd.Apply => isAccessorLHS(lhs.fun) + case lhs: untpd.TypeApply => isAccessorLHS(lhs.fun) + case lhs: untpd.RefTree => lhs.name.is(InlineAccessorName) + case untpd.TypedSplice(lhs1) => lhs1.symbol.name.is(InlineAccessorName) + case _ => false + } + + val tree1 = super.transform(tree) + tree1 match { + case This(_) => + adjustLeaf(tree1) + case tree1: untpd.Ident => + adjustQualifier(adjustLeaf(adjustForAccessor(tree1))) + case tree1: untpd.Select => + adjustForAccessor(tree1) + case Assign(lhs, rhs) if isAccessorLHS(lhs) => + cpy.Apply(tree1)(lhs, rhs :: Nil) + case tree: untpd.DerivedTypeTree => + inlining.println(i"inlining derived $tree --> ${ctx.typer.typed(tree)}") + untpd.TypedSplice(ctx.typer.typed(tree)) + case _ => + tree1 + } + } + } + val implicitBindings = + for (iref <- referenced.implicitRefs.toList) yield { + val localImplicit = iref.symbol.asTerm.copy( + owner = inlineMethod, + name = UniqueInlineName.fresh(iref.symbol.name.asTermName), + flags = Implicit | Method | Stable, + info = iref.tpe.widen.ensureMethodic, + coord = inlineMethod.pos).asTerm + polyDefDef(localImplicit, tps => vrefss => + iref.appliedToTypes(tps).appliedToArgss(vrefss)) + } + val untpdSplice = tpd.UntypedSplice(addRefs.transform(original)).withType(typed.tpe) + seq(implicitBindings, untpdSplice) + } +} \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 5a32fcf19364..fe902a13de51 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -71,6 +71,19 @@ object ProtoTypes { if (!res) ctx.typerState.resetConstraintTo(savedConstraint) res } + + /** Constrain result with special case if `meth` is a transparent method in an inlineable context. + * In that case, we should always succeed and not constrain type parameters in the expected type, + * because the actual return type can be a subtype of the currently known return type. + * However, we should constrain parameters of the declared return type. This distinction is + * achieved by replacing expected type parameters with wildcards. + */ + def constrainResult(meth: Symbol, mt: Type, pt: Type)(implicit ctx: Context): Boolean = + if (Inliner.isTransparentInlineable(meth)) { + constrainResult(mt, wildApprox(pt)) + true + } + else constrainResult(mt, pt) } object NoViewsAllowed extends Compatibility { @@ -535,7 +548,7 @@ object ProtoTypes { /** Approximate occurrences of parameter types and uninstantiated typevars * by wildcard types. */ - final def wildApprox(tp: Type, theMap: WildApproxMap, seen: Set[TypeParamRef])(implicit ctx: Context): Type = tp match { + private def wildApprox(tp: Type, theMap: WildApproxMap, seen: Set[TypeParamRef])(implicit ctx: Context): Type = tp match { case tp: NamedType => // default case, inlined for speed if (tp.symbol.isStatic || (tp.prefix `eq` NoPrefix)) tp else tp.derivedSelect(wildApprox(tp.prefix, theMap, seen)) @@ -604,6 +617,8 @@ object ProtoTypes { .mapOver(tp) } + final def wildApprox(tp: Type)(implicit ctx: Context): Type = wildApprox(tp, null, Set.empty) + @sharable object AssignProto extends UncachedGroundType with MatchAlways private[ProtoTypes] class WildApproxMap(val seen: Set[TypeParamRef])(implicit ctx: Context) extends TypeMap { diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index d6ea4b9d1e29..50c3efc5bad9 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -143,7 +143,7 @@ object RefChecks { * 1.8.1 M's type is a subtype of O's type, or * 1.8.2 M is of type []S, O is of type ()T and S <: T, or * 1.8.3 M is of type ()S, O is of type []T and S <: T, or - * 1.9 M must not be a Dotty macro def + * 1.9 M must not be a typelevel def or a Dotty macro def * 1.10. If M is a 2.x macro def, O cannot be deferred unless there's a concrete method overriding O. * 1.11. If M is not a macro def, O cannot be a macro def. * 2. Check that only abstract classes have deferred members @@ -376,6 +376,8 @@ object RefChecks { overrideError("may not override a non-lazy value") } else if (other.is(Lazy) && !other.isRealMethod && !member.is(Lazy)) { overrideError("must be declared lazy to override a lazy value") + } else if (member.is(Erased) && member.allOverriddenSymbols.forall(_.is(Deferred))) { // (1.9) + overrideError("is an erased method, may not override only deferred methods") } else if (member.is(Macro, butNot = Scala2x)) { // (1.9) overrideError("is a macro, may not override anything") } else if (other.is(Deferred) && member.is(Scala2Macro) && member.extendedOverriddenSymbols.forall(_.is(Deferred))) { // (1.10) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index a45c8174395a..b367146e3ffc 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -476,9 +476,9 @@ class Typer extends Namer case pt: SelectionProto if pt.name == nme.CONSTRUCTOR => true case _ => false } - val enclosingInlineable = ctx.owner.ownersIterator.findSymbol(_.isInlineableMethod) - if (enclosingInlineable.exists && !Inliner.isLocal(qual1.symbol, enclosingInlineable)) - ctx.error(SuperCallsNotAllowedInline(enclosingInlineable), tree.pos) + val enclosingTransparent = ctx.owner.ownersIterator.findSymbol(_.isTransparentMethod) + if (enclosingTransparent.exists && !PrepareTransparent.isLocal(qual1.symbol, enclosingTransparent)) + ctx.error(SuperCallsNotAllowedTransparent(enclosingTransparent), tree.pos) pt match { case pt: SelectionProto if pt.name.isTypeName => qual1 // don't do super references for types; they are meaningless anyway @@ -567,7 +567,8 @@ class Typer extends Namer def typedTpt = checkSimpleKinded(typedType(tree.tpt)) def handlePattern: Tree = { val tpt1 = typedTpt - if (!ctx.isAfterTyper) constrainPatternType(tpt1.tpe, pt)(ctx.addMode(Mode.GADTflexible)) + if (!ctx.isAfterTyper && pt != defn.ImplicitScrutineeTypeRef) + constrainPatternType(tpt1.tpe, pt)(ctx.addMode(Mode.GADTflexible)) // special case for an abstract type that comes with a class tag tryWithClassTag(ascription(tpt1, isWildcard = true), pt) } @@ -966,55 +967,70 @@ class Typer extends Namer assignType(cpy.Closure(tree)(env1, meth1, target), meth1, target) } - def typedMatch(tree: untpd.Match, pt: Type)(implicit ctx: Context) = track("typedMatch") { + def typedMatch(tree: untpd.Match, pt: Type)(implicit ctx: Context): Tree = track("typedMatch") { tree.selector match { case EmptyTree => val (protoFormals, _) = decomposeProtoFunction(pt, 1) val unchecked = pt.isRef(defn.PartialFunctionClass) typed(desugar.makeCaseLambda(tree.cases, protoFormals.length, unchecked) withPos tree.pos, pt) + case id @ untpd.ImplicitScrutinee() => + if (tree.getAttachment(PrepareTransparent.TopLevelMatch).isEmpty) + ctx.error(em"implicit match cannot be used here; it must occur as a toplevel match of a transparent method", tree.pos) + val sel1 = id.withType(defn.ImplicitScrutineeTypeRef) + typedMatchFinish(tree, sel1, sel1.tpe, pt) case _ => val sel1 = typedExpr(tree.selector) val selType = fullyDefinedType(sel1.tpe, "pattern selector", tree.pos).widen - - val cases1 = harmonic(harmonize)(typedCases(tree.cases, selType, pt.notApplied)) - .asInstanceOf[List[CaseDef]] - assignType(cpy.Match(tree)(sel1, cases1), cases1) + typedMatchFinish(tree, sel1, selType, pt) } } - def typedCases(cases: List[untpd.CaseDef], selType: Type, pt: Type)(implicit ctx: Context) = { + // Overridden in InlineTyper for transparent matches + def typedMatchFinish(tree: untpd.Match, sel: Tree, selType: Type, pt: Type)(implicit ctx: Context): Tree = { + val cases1 = harmonic(harmonize)(typedCases(tree.cases, selType, pt.notApplied)) + .asInstanceOf[List[CaseDef]] + assignType(cpy.Match(tree)(sel, cases1), cases1) + } - /** gadtSyms = "all type parameters of enclosing methods that appear - * non-variantly in the selector type" todo: should typevars - * which appear with variances +1 and -1 (in different - * places) be considered as well? - */ - val gadtSyms: Set[Symbol] = trace(i"GADT syms of $selType", gadts) { - val accu = new TypeAccumulator[Set[Symbol]] { - def apply(tsyms: Set[Symbol], t: Type): Set[Symbol] = { - val tsyms1 = t match { - case tr: TypeRef if (tr.symbol is TypeParam) && tr.symbol.owner.isTerm && variance == 0 => - tsyms + tr.symbol - case _ => - tsyms - } - foldOver(tsyms1, t) + /** gadtSyms = "all type parameters of enclosing methods that appear + * non-variantly in the selector type" todo: should typevars + * which appear with variances +1 and -1 (in different + * places) be considered as well? + */ + def gadtSyms(selType: Type)(implicit ctx: Context): Set[Symbol] = trace(i"GADT syms of $selType", gadts) { + val accu = new TypeAccumulator[Set[Symbol]] { + def apply(tsyms: Set[Symbol], t: Type): Set[Symbol] = { + val tsyms1 = t match { + case tr: TypeRef if (tr.symbol is TypeParam) && tr.symbol.owner.isTerm && variance == 0 => + tsyms + tr.symbol + case _ => + tsyms } + foldOver(tsyms1, t) } - accu(Set.empty, selType) } - - cases mapconserve (typedCase(_, pt, selType, gadtSyms)) + accu(Set.empty, selType) } - /** Type a case. */ - def typedCase(tree: untpd.CaseDef, pt: Type, selType: Type, gadtSyms: Set[Symbol])(implicit ctx: Context): CaseDef = track("typedCase") { - val originalCtx = ctx - + /** Context with fresh GADT bounds for all gadtSyms */ + def gadtContext(gadtSyms: Set[Symbol])(implicit ctx: Context) = { val gadtCtx = ctx.fresh.setFreshGADTBounds for (sym <- gadtSyms) if (!gadtCtx.gadt.bounds.contains(sym)) gadtCtx.gadt.setBounds(sym, TypeBounds.empty) + gadtCtx + } + + def typedCases(cases: List[untpd.CaseDef], selType: Type, pt: Type)(implicit ctx: Context) = { + val gadts = gadtSyms(selType) + cases.mapconserve(typedCase(_, selType, pt, gadts)) + } + + /** Type a case. */ + def typedCase(tree: untpd.CaseDef, selType: Type, pt: Type, gadtSyms: Set[Symbol])(implicit ctx: Context): CaseDef = track("typedCase") { + val originalCtx = ctx + + val gadtCtx = gadtContext(gadtSyms) /** - strip all instantiated TypeVars from pattern types. * run/reducable.scala is a test case that shows stripping typevars is necessary. @@ -1072,8 +1088,8 @@ class Typer extends Namer (EmptyTree, WildcardType) } else if (owner != cx.outer.owner && owner.isRealMethod) { - if (owner.isInlineableMethod) - (EmptyTree, errorType(NoReturnFromInline(owner), tree.pos)) + if (owner.isTransparentMethod) + (EmptyTree, errorType(NoReturnFromTransparent(owner), tree.pos)) else if (!owner.isCompleted) (EmptyTree, errorType(MissingReturnTypeWithReturnStatement(owner), tree.pos)) else { @@ -1143,7 +1159,7 @@ class Typer extends Namer } } - def typedInlined(tree: untpd.Inlined, pt: Type)(implicit ctx: Context): Inlined = { + def typedInlined(tree: untpd.Inlined, pt: Type)(implicit ctx: Context): Tree = { val (exprCtx, bindings1) = typedBlockStats(tree.bindings) val expansion1 = typed(tree.expansion, pt)(inlineContext(tree.call)(exprCtx)) assignType(cpy.Inlined(tree)(tree.call, bindings1.asInstanceOf[List[MemberDef]], expansion1), @@ -1327,6 +1343,7 @@ class Typer extends Namer if (body1.tpe.isInstanceOf[TermRef]) pt1 else body1.tpe.underlyingIfRepeated(isJava = false) val sym = ctx.newPatternBoundSymbol(tree.name, symTp, tree.pos) + if (pt == defn.ImplicitScrutineeTypeRef) sym.setFlag(Implicit) if (ctx.mode.is(Mode.InPatternAlternative)) ctx.error(i"Illegal variable ${sym.name} in pattern alternative", tree.pos) assignType(cpy.Bind(tree)(tree.name, body1), sym) @@ -1368,17 +1385,17 @@ class Typer extends Namer typed(annot, defn.AnnotationType) } - def typedValDef(vdef: untpd.ValDef, sym: Symbol)(implicit ctx: Context) = track("typedValDef") { + def typedValDef(vdef: untpd.ValDef, sym: Symbol)(implicit ctx: Context): Tree = track("typedValDef") { val ValDef(name, tpt, _) = vdef completeAnnotations(vdef, sym) val tpt1 = checkSimpleKinded(typedType(tpt)) val rhs1 = vdef.rhs match { case rhs @ Ident(nme.WILDCARD) => rhs withType tpt1.tpe - case rhs => normalizeErasedRhs(typedExpr(rhs, tpt1.tpe), sym) + case rhs => typedExpr(rhs, tpt1.tpe) } val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) - if (sym.is(Inline, butNot = DeferredOrTermParamOrAccessor)) - checkInlineConformant(rhs1, isFinal = sym.is(Final), em"right-hand side of inline $sym") + if (sym.is(Transparent, butNot = DeferredOrTermParamOrAccessor)) + checkTransparentConformant(rhs1, isFinal = sym.is(Final), em"right-hand side of transparent $sym") patchIfLazy(vdef1) patchFinalVals(vdef1) vdef1 @@ -1393,7 +1410,7 @@ class Typer extends Namer patch(Position(toUntyped(vdef).pos.start), "@volatile ") } - /** Adds inline to final vals with idempotent rhs + /** Adds transparent to final vals with idempotent rhs * * duplicating scalac behavior: for final vals that have rhs as constant, we do not create a field * and instead return the value. This seemingly minor optimization has huge effect on initialization @@ -1409,7 +1426,7 @@ class Typer extends Namer } val sym = vdef.symbol sym.info match { - case info: ConstantType if isFinalInlinableVal(sym) && !ctx.settings.YnoInline.value => sym.setFlag(Inline) + case info: ConstantType if isFinalInlinableVal(sym) && !ctx.settings.YnoInline.value => sym.setFlag(Transparent) case _ => } } @@ -1437,10 +1454,13 @@ class Typer extends Namer (tparams1, sym.owner.typeParams).zipped.foreach ((tdef, tparam) => rhsCtx.gadt.setBounds(tdef.symbol, TypeAlias(tparam.typeRef))) } - val rhs1 = normalizeErasedRhs(typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx), sym) + if (sym.isTransparentMethod) { + rhsCtx = rhsCtx.addMode(Mode.TransparentBody) + PrepareTransparent.markTopLevelMatches(sym, ddef.rhs) + } + val rhs1 = typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx) - // Overwrite inline body to make sure it is not evaluated twice - if (sym.isInlineableMethod) Inliner.registerInlineInfo(sym, _ => rhs1) + if (sym.isTransparentInlineable) PrepareTransparent.registerInlineInfo(sym, ddef.rhs, _ => rhs1) if (sym.isConstructor && !sym.isPrimaryConstructor) for (param <- tparams1 ::: vparamss1.flatten) @@ -1671,7 +1691,7 @@ class Typer extends Namer } def typedTypedSplice(tree: untpd.TypedSplice)(implicit ctx: Context): Tree = - tree.tree match { + tree.splice match { case tree1: TypeTree => tree1 // no change owner necessary here ... case tree1: Ident => tree1 // ... or here, since these trees cannot contain bindings case tree1 => @@ -1909,7 +1929,10 @@ class Typer extends Namer case none => typed(mdef) match { case mdef1: DefDef if Inliner.hasBodyToInline(mdef1.symbol) => - buf += inlineExpansion(mdef1) + assert(mdef1.symbol.isTransparentInlineable, mdef.symbol) + Inliner.bodyToInline(mdef1.symbol) // just make sure accessors are computed, + buf += mdef1 // but keep original definition, since inline-expanded code + // is pickled in this case. case mdef1 => import untpd.modsDeco mdef match { @@ -1941,7 +1964,7 @@ class Typer extends Namer checkEnumCompanions(traverse(stats)(localCtx), enumContexts) } - /** Given an inline method `mdef`, the method rewritten so that its body + /** Given a transparent method `mdef`, the method rewritten so that its body * uses accessors to access non-public members. * Overwritten in Retyper to return `mdef` unchanged. */ @@ -2103,15 +2126,13 @@ class Typer extends Namer * If all this fails, error * Parameters as for `typedUnadapted`. */ - def adapt(tree: Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): Tree = /*>|>*/ track("adapt") /*<|<*/ { - def showWithType(x: Any) = x match { - case tree: tpd.Tree @unchecked => i"$tree of type ${tree.tpe}" - case _ => String.valueOf(x) + def adapt(tree: Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): Tree = track("adapt") { + trace(i"adapting $tree to $pt", typr, show = true) { + adapt1(tree, pt, locked) } - adapt1(tree, pt, locked) } - def adapt(tree: Tree, pt: Type)(implicit ctx: Context): Tree = { + final def adapt(tree: Tree, pt: Type)(implicit ctx: Context): Tree = { adapt(tree, pt, ctx.typerState.ownedVars) } @@ -2123,7 +2144,9 @@ class Typer extends Namer def readaptSimplified(tree: Tree)(implicit ctx: Context) = readapt(simplify(tree, pt, locked)) def missingArgs(mt: MethodType) = { - ctx.error(MissingEmptyArgumentList(methPart(tree).symbol), tree.pos) + val meth = methPart(tree).symbol + if (mt.paramNames.length == 0) ctx.error(MissingEmptyArgumentList(meth), tree.pos) + else ctx.error(em"missing arguments for $meth", tree.pos) tree.withType(mt.resultType) } @@ -2229,18 +2252,7 @@ class Typer extends Namer arg :: implicitArgs(formals1) } } - def eraseErasedArgs(args: List[Tree]): List[Tree] = { - if (!wtp.isErasedMethod) args - else args.map { arg => - arg.tpe match { - case tpe if tpe.isStable => arg - case _: AmbiguousImplicits => arg - case tpe => defaultValue(tpe) - } - } - } - val args = eraseErasedArgs(implicitArgs(wtp.paramInfos)) - + val args = implicitArgs(wtp.paramInfos) def propagatedFailure(args: List[Tree]): Type = args match { case arg :: args1 => @@ -2334,10 +2346,12 @@ class Typer extends Namer // Reasons NOT to eta expand: // - we reference a constructor + // - we reference a typelevel method // - we are in a pattern // - the current tree is a synthetic apply which is not expandable (eta-expasion would simply undo that) if (arity >= 0 && !tree.symbol.isConstructor && + !tree.symbol.is(TransparentMethod) && !ctx.mode.is(Mode.Pattern) && !(isSyntheticApply(tree) && !isExpandableApply)) simplify(typed(etaExpand(tree, wtp, arity), pt), pt, locked) @@ -2349,7 +2363,7 @@ class Typer extends Namer missingArgs(wtp) } - def adaptNoArgsOther(wtp: Type) = { + def adaptNoArgsOther(wtp: Type): Tree = { ctx.typeComparer.GADTused = false if (defn.isImplicitFunctionClass(wtp.underlyingClassRef(refinementOK = false).classSymbol) && !untpd.isImplicitClosure(tree) && @@ -2364,16 +2378,14 @@ class Typer extends Namer checkEqualityEvidence(tree, pt) tree } + else if (Inliner.isInlineable(tree)) { + tree.tpe <:< wildApprox(pt) + readaptSimplified(Inliner.inlineCall(tree, pt)) + } else if (tree.tpe <:< pt) { - if (pt.hasAnnotation(defn.InlineParamAnnot)) - checkInlineConformant(tree, isFinal = false, "argument to inline parameter") - if (Inliner.hasBodyToInline(tree.symbol) && - !ctx.owner.ownersIterator.exists(_.isInlineableMethod) && - !ctx.settings.YnoInline.value && - !ctx.isAfterTyper && - !ctx.reporter.hasErrors) - readaptSimplified(Inliner.inlineCall(tree, pt)) - else if (ctx.typeComparer.GADTused && pt.isValueType) + if (pt.hasAnnotation(defn.TransparentParamAnnot)) + checkTransparentConformant(tree, isFinal = false, "argument to transparent parameter") + if (ctx.typeComparer.GADTused && pt.isValueType) // Insert an explicit cast, so that -Ycheck in later phases succeeds. // I suspect, but am not 100% sure that this might affect inferred types, // if the expected type is a supertype of the GADT bound. It would be good to come @@ -2405,7 +2417,7 @@ class Typer extends Namer def adaptNoArgs(wtp: Type): Tree = { val ptNorm = underlyingApplied(pt) lazy val functionExpected = defn.isFunctionType(ptNorm) - lazy val resultMatch = constrainResult(wtp, followAlias(pt)) + lazy val resultMatch = constrainResult(tree.symbol, wtp, followAlias(pt)) wtp match { case wtp: ExprType => readaptSimplified(tree.withType(wtp.resultType)) diff --git a/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala index 464b427c8993..b639adbfc66b 100644 --- a/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala +++ b/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala @@ -8,7 +8,7 @@ class InlineBytecodeTests extends DottyBytecodeTest { @Test def inlineUnit = { val source = """ |class Foo { - | inline def foo: Int = 1 + | transparent def foo: Int = 1 | @forceInline def bar: Int = 1 | | def meth1: Unit = foo diff --git a/compiler/test/dotty/tools/dotc/parsing/ModifiersParsingTest.scala b/compiler/test/dotty/tools/dotc/parsing/ModifiersParsingTest.scala index 32e8535e179a..c560d1905cff 100644 --- a/compiler/test/dotty/tools/dotc/parsing/ModifiersParsingTest.scala +++ b/compiler/test/dotty/tools/dotc/parsing/ModifiersParsingTest.scala @@ -120,7 +120,7 @@ class ModifiersParsingTest { | final val c = ??? | | abstract override def f: Boolean - | inline def g(n: Int) = ??? + | transparent def g(n: Int) = ??? | } """.stripMargin @@ -128,12 +128,12 @@ class ModifiersParsingTest { assert(source.field("b").modifiers == List(Mod.Lazy(), Mod.Private())) assert(source.field("c").modifiers == List(Mod.Final())) assert(source.field("f").modifiers == List(Mod.Abstract(), Mod.Override())) - assert(source.field("g").modifiers == List(Mod.Inline())) + assert(source.field("g").modifiers == List(Mod.Transparent())) } @Test def paramDef = { - var source: Tree = "def f(inline a: Int) = ???" - assert(source.defParam(0).modifiers == List(Mod.Inline())) + var source: Tree = "def f(transparent a: Int) = ???" + assert(source.defParam(0).modifiers == List(Mod.Transparent())) source = "def f(implicit a: Int, b: Int) = ???" assert(source.defParam(0).modifiers == List(Mod.Implicit())) diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index 3fad2c2bc2a8..50cf7f0860df 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -826,7 +826,7 @@ class ErrorMessagesTests extends ErrorMessagesTest { |} | |class B extends A { - | inline def bar(): Unit = super.foo() + | transparent def bar(): Unit = super.foo() |} """.stripMargin } @@ -834,7 +834,7 @@ class ErrorMessagesTests extends ErrorMessagesTest { implicit val ctx: Context = ictx assertMessageCount(1, messages) val err :: Nil = messages - val SuperCallsNotAllowedInline(symbol) = err + val SuperCallsNotAllowedTransparent(symbol) = err assertEquals("method bar", symbol.show) } @@ -935,7 +935,7 @@ class ErrorMessagesTests extends ErrorMessagesTest { @Test def noReturnInInline = checkMessagesAfter(FrontEnd.name) { """class BadFunction { - | inline def usesReturn: Int = { return 42 } + | transparent def usesReturn: Int = { return 42 } |} """.stripMargin }.expect { (ictx, messages) => @@ -943,7 +943,7 @@ class ErrorMessagesTests extends ErrorMessagesTest { assertMessageCount(1, messages) - val NoReturnFromInline(method) :: Nil = messages + val NoReturnFromTransparent(method) :: Nil = messages assertEquals("method usesReturn", method.show) } diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index f275cbcd497d..b4c5d19c6b16 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -175,6 +175,7 @@ Expr1 ::= ‘if’ ‘(’ Expr ‘)’ {nl} Expr [[semi] ‘else | SimpleExpr1 ArgumentExprs ‘=’ Expr Assign(expr, expr) | PostfixExpr [Ascription] | PostfixExpr ‘match’ ‘{’ CaseClauses ‘}’ Match(expr, cases) -- point on match + | ‘implicit’ ‘match’ ‘{’ ImplicitCaseClauses ‘}’ Ascription ::= ‘:’ InfixType Typed(expr, tp) | ‘:’ Annotation {Annotation} Typed(expr, Annotated(EmptyTree, annot)*) Catches ::= ‘catch’ Expr @@ -223,6 +224,8 @@ Guard ::= ‘if’ PostfixExpr CaseClauses ::= CaseClause { CaseClause } Match(EmptyTree, cases) CaseClause ::= ‘case’ (Pattern [Guard] ‘=>’ Block | INT) CaseDef(pat, guard?, block) // block starts at => +ImplicitCaseClauses ::= ImplicitCaseClause { ImplicitCaseClause } +ImplicitCaseClause ::= ‘case’ PatVar [‘:’ RefinedType] [Guard] ‘=>’ Block Pattern ::= Pattern1 { ‘|’ Pattern1 } Alternative(pats) Pattern1 ::= PatVar ‘:’ RefinedType Bind(name, Typed(Ident(wildcard), tpe)) @@ -263,14 +266,14 @@ ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [FunArgMods] ClsParams ‘ ClsParamClause ::= [nl] ‘(’ [ClsParams] ‘)’ ClsParams ::= ClsParam {‘,’ ClsParam} ClsParam ::= {Annotation} ValDef(mods, id, tpe, expr) -- point of mods on val/var - [{Modifier} (‘val’ | ‘var’) | ‘inline’] Param + [{Modifier} (‘val’ | ‘var’) | ‘transparent’] Param Param ::= id ‘:’ ParamType [‘=’ Expr] | INT DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [FunArgMods] DefParams ‘)’] DefParamClause ::= [nl] ‘(’ [DefParams] ‘)’ DefParams ::= DefParam {‘,’ DefParam} -DefParam ::= {Annotation} [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. +DefParam ::= {Annotation} [‘transparent’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. ``` ### Bindings and Imports diff --git a/docs/docs/reference/dropped/weak-conformance.md b/docs/docs/reference/dropped/weak-conformance.md index 0c796f9e2cd9..a6c8d68d678c 100644 --- a/docs/docs/reference/dropped/weak-conformance.md +++ b/docs/docs/reference/dropped/weak-conformance.md @@ -66,7 +66,7 @@ assigning a type to a constant expression. The new rule is: __Examples:__ - inline val b = 33 + transparent val b = 33 def f(): Int = b + 1 List(b, 33, 'a') : List[Int] List(b, 33, 'a', f()) : List[Int] diff --git a/docs/docs/reference/inline.md b/docs/docs/reference/inline.md index 372bbeda73c1..56400170c8a9 100644 --- a/docs/docs/reference/inline.md +++ b/docs/docs/reference/inline.md @@ -1,20 +1,20 @@ --- layout: doc-page -title: Inline +title: Transparent --- -`inline` is a new modifier that guarantees that a definition will be +`transparent` is a new modifier that guarantees that a definition will be inlined at the point of use. Example: object Config { - inline val logging = false + transparent val logging = false } object Logger { private var indent = 0 - inline def log[T](msg: => String)(op: => T): T = + transparent def log[T](msg: => String)(op: => T): T = if (Config.logging) { println(s"${" " * indent}start $msg") indent += 1 @@ -26,15 +26,15 @@ inlined at the point of use. Example: else op } -The `Config` object contains a definition of an `inline` value +The `Config` object contains a definition of an `transparent` value `logging`. This means that `logging` is treated as a constant value, equivalent to its right-hand side `false`. The right-hand side of such -an inline val must itself be a [constant +an transparent val must itself be a [constant expression](#the-definition-of-constant-expression). Used in this way, -`inline` is equivalent to Java and Scala 2's `final`. `final` meaning +`transparent` is equivalent to Java and Scala 2's `final`. `final` meaning "constant" is still supported in Dotty, but will be phased out. -The `Logger` object contains a definition of an `inline` method `log`. +The `Logger` object contains a definition of an `transparent` method `log`. This method will always be inlined at the point of call. In the inlined code, an if-then-else with a constant condition will be @@ -57,29 +57,43 @@ If `Config.logging == false`, this will be rewritten to } Note that the arguments corresponding to the parameters `msg` and `op` -of the inline method `log` are defined before the inlined body (which +of the transparent method `log` are defined before the inlined body (which is in this case simply `op`). By-name parameters of the inlined method correspond to `def` bindings whereas by-value parameters correspond to `val` bindings. So if `log` was defined like this: - inline def log[T](msg: String)(op: => T): T = ... + transparent def log[T](msg: String)(op: => T): T = ... we'd get val msg = s"factorial($n)" -instead. This behavior is designed so that calling an inline method is +instead. This behavior is designed so that calling a transparent method is semantically the same as calling a normal method: By-value arguments are evaluated before the call whereas by-name arguments are evaluated each time they are referenced. As a consequence, it is often -preferable to make arguments of inline methods by-name in order to +preferable to make arguments of transparent methods by-name in order to avoid unnecessary evaluations. -Inline methods can be recursive. For instance, when called with a constant +For instance, here is how we can define a zero-overhead `foreach` method +that translates into a straightforward while loop without any indirection or +overhead: + + transparent def foreach(op: => Int => Unit): Unit = { + var i = from + while (i < end) { + op(i) + i += 1 + } + } + +By contrast, if `op` is a call-by-value parameter, it would be evaluated separately as a closure. + +Transparent methods can be recursive. For instance, when called with a constant exponent `n`, the following method for `power` will be implemented by -straight inline code without any loop or recursion. +straight transparent code without any loop or recursion. - inline def power(x: Double, n: Int): Double = + transparent def power(x: Double, n: Int): Double = if (n == 0) 1.0 else if (n == 1) x else { @@ -96,44 +110,26 @@ straight inline code without any loop or recursion. // val y3 = y2 * x // ^5 // y3 * y3 // ^10 -Parameters of inline methods can themselves be marked `inline`. This means -that the argument of an inline method is itself inlined in the inlined body of -the method. Using this scheme, we can define a zero-overhead `foreach` method -that translates into a straightforward while loop without any indirection or -overhead: - - inline def foreach(inline op: Int => Unit): Unit = { - var i = from - while (i < end) { - op(i) - i += 1 - } - } +Parameters of transparent methods can themselves be marked `transparent`. This means +that actual arguments to these parameters must be constant expressions. ### Relationship to `@inline`. -Existing Scala defines a `@inline` annotation which is used as a hint -for the backend to inline. For most purposes, this annotation is -superseded by the `inline` modifier. The modifier is more powerful -than the annotation: Expansion is guaranteed instead of best effort, +Scala also defines a `@inline` annotation which is used as a hint +for the backend to inline. The `transparent` modifier is a more powerful +option: Expansion is guaranteed instead of best effort, it happens in the frontend instead of in the backend, and it also applies -to method arguments and recursive methods. - -Since `inline` is now a keyword, it would be a syntax error to write -`@inline`. However, one can still refer to the annotation by putting -it in backticks, i.e. - - @`inline` def ... +to recursive methods. -The Dotty compiler ignores `@inline` annotated definitions. To cross -compile between both Dotty and Scalac, we introduce a new `@forceInline` -annotation which is equivalent to the new `inline` modifier. Note that -Scala 2 ignores the `@forceInLine` annotation, and one must use both -annotations to inline across the two compilers (i.e. `@forceInline @inline`). +To cross compile between both Dotty and Scalac, we introduce a new `@forceInline` +annotation which is equivalent to the new `transparent` modifier. Note that +Scala 2 ignores the `@forceInline` annotation, so one must use both +annotations to guarantee inlining for Dotty and at the same time hint inlining +for Scala 2 (i.e. `@forceInline @inline`). ### The definition of constant expression -Right-hand sides of inline values and of arguments for inline parameters +Right-hand sides of transparent values and of arguments for transparent parameters must be constant expressions in the sense defined by the [SLS § 6.24](https://www.scala-lang.org/files/archive/spec/2.12/06-expressions.html#constant-expressions), including "platform-specific" extensions such as constant folding of @@ -141,5 +137,5 @@ pure numeric computations. ### Reference -For more info, see [PR #1492](https://github.com/lampepfl/dotty/pull/1492) and -[Scala SIP 28](http://docs.scala-lang.org/sips/pending/inline-meta.html). +For more info, see [PR #4768](https://github.com/lampepfl/dotty/pull/4768), which explains how +transparent methods can be used for typelevel programming and code specialization. diff --git a/docs/docs/reference/principled-meta-programming.md b/docs/docs/reference/principled-meta-programming.md index a3b972618e97..f4b14fcc9b17 100644 --- a/docs/docs/reference/principled-meta-programming.md +++ b/docs/docs/reference/principled-meta-programming.md @@ -19,14 +19,14 @@ operations: quotation and splicing. Quotation is expressed as `'(...)` or `'{...}` for expressions (both forms are equivalent) and as `'[...]` for types. Splicing is expressed as a prefix `~` operator. -For example, the code below presents an inline function `assert` +For example, the code below presents a transparent function `assert` which calls at compile-time a method `assertImpl` with a boolean expression tree as argument. `assertImpl` evaluates the expression and prints it again in an error message if it evaluates to `false`. import scala.quoted._ - inline def assert(expr: => Boolean): Unit = + transparent def assert(expr: => Boolean): Unit = ~ assertImpl('(expr)) def assertImpl(expr: Expr[Boolean]) = @@ -220,10 +220,10 @@ is handled by the compiler, using the algorithm sketched above. ### Example Expansion -Assume an `Array` class with an inline `map` method that forwards to a macro implementation. +Assume an `Array` class with a transparent `map` method that forwards to a macro implementation. class Array[T] { - inline def map[U](f: T => U): Array[U] = ~ Macros.mapImpl[T, U]('[U], '(this), '(f)) + transparent def map[U](f: T => U): Array[U] = ~ Macros.mapImpl[T, U]('[U], '(this), '(f)) } Here’s the definition of the `mapImpl` macro, which takes quoted types and expressions to a quoted expression: @@ -303,11 +303,11 @@ Here’s an application of `map` and how it rewrites to optimized code: ys } -### Relationship with Inline and Macros +### Relationship with Transparent and Macros Seen by itself, principled meta-programming looks more like a framework for staging than one for compile-time meta programming with -macros. But combined with Dotty’s `inline` it can be turned into a +macros. But combined with Dotty’s `transparent` feature it can be turned into a compile-time system. The idea is that macro elaboration can be understood as a combination of a macro library and a quoted program. For instance, here’s the `assert` macro again together with a @@ -315,7 +315,7 @@ program that calls `assert`. object Macros { - inline def assert(expr: => Boolean): Unit = + transparent def assert(expr: => Boolean): Unit = ~ assertImpl('(expr)) def assertImpl(expr: Expr[Boolean]) = @@ -363,7 +363,7 @@ If `program` is treated as a quoted expression, the call to program are conceptualized as local definitions. But what about the call from `assert` to `assertImpl`? Here, we need a -tweak of the typing rules. An inline function such as `assert` that +tweak of the typing rules. A transparent function such as `assert` that contains a splice operation outside an enclosing quote is called a _macro_. Macros are supposed to be expanded in a subsequent phase, i.e. in a quoted context. Therefore, they are also type checked as if @@ -372,13 +372,13 @@ they were in a quoted context. For instance, the definition of the call from `assert` to `assertImpl` phase-correct, even if we assume that both definitions are local. -The second role of `inline` in Dotty is to mark a `val` that is +The second role of `transparent` in Dotty is to mark a `val` that is either a constant or is a parameter that will be a constant when instantiated. This aspect is also important for macro expansion. To illustrate this, consider an implementation of the `power` function that makes use of a statically known exponent: - inline def power(inline n: Int, x: Double) = ~powerCode(n, '(x)) + transparent def power(transparent n: Int, x: Double) = ~powerCode(n, '(x)) private def powerCode(n: Int, x: Expr[Double]): Expr[Double] = if (n == 0) '(1.0) @@ -390,13 +390,13 @@ The reference to `n` as an argument in `~powerCode(n, '(x))` is not phase-consistent, since `n` appears in a splice without an enclosing quote. Normally that would be a problem because it means that we need the _value_ of `n` at compile time, which is not available for general -parameters. But since `n` is an inline parameter of a macro, we know +parameters. But since `n` is a transparent parameter of a macro, we know that at the macro’s expansion point `n` will be instantiated to a constant, so the value of `n` will in fact be known at this point. To reflect this, we loosen the phase consistency requirements as follows: - - If `x` is an inline value (or an inline parameter of an inline + - If `x` is a transparent value (or a transparent parameter of a transparent function) of type Boolean, Byte, Short, Int, Long, Float, Double, Char or String, it can be accessed in all contexts where the number of splices minus the number of quotes between use and definition @@ -433,7 +433,7 @@ Providing an interpreter for the full language is quite difficult, and it is even more difficult to make that interpreter run efficiently. So we currently impose the following restrictions on the use of splices. - 1. A top-level splice must appear in an inline function (turning that function + 1. A top-level splice must appear in a transparent function (turning that function into a macro) 2. The splice must call a previously compiled method. @@ -762,14 +762,14 @@ is studied [separately](simple-smp.md). The meta-programming framework as presented and currently implemented is quite restrictive in that it does not allow for the inspection of quoted expressions and types. It’s possible to work around this by providing all necessary -information as normal, unquoted inline parameters. But we would gain +information as normal, unquoted transparent parameters. But we would gain more flexibility by allowing for the inspection of quoted code with pattern matching. This opens new possibilities. For instance, here is a version of `power` that generates the multiplications directly if the exponent is statically known and falls back to the dynamic implementation of power otherwise. - inline def power(n: Int, x: Double): Double = ~{ + transparent def power(n: Int, x: Double): Double = ~{ '(n) match { case Constant(n1) => powerCode(n1, '(x)) case _ => '{ dynamicPower(n, x) } diff --git a/docs/docs/typelevel.md b/docs/docs/typelevel.md new file mode 100644 index 000000000000..8e09d9971e09 --- /dev/null +++ b/docs/docs/typelevel.md @@ -0,0 +1,519 @@ +# Functional Typelevel Programming in Scala + +This is a working draft document for discussing language constructs in +support of typelevel programming in Scala 3. + +## State of the Art + +Currently, typelevel programming in Scala is mainly done using implicits. +Proof search for finding implicit arguments can be used to compute new, interesting types. +This results in a programming style much like Prolog. Amazing feats have +been achieved using this scheme, but things are certainly far from +ideal. In particular: + + - The logic programming style requires a shift of mindset compared to the + usual functional programming style in Scala. + - The ways to control implicit search are underdeveloped, + leading to complicated schemes, requiring rule prioritization or other hacks. + - Because of their conceptual complexity the resulting typelevel programs are often + fragile. + - Error diagnostics are usually very bad, making programming with implicits somewhat + of a black art. Dotty has greatly improved error dignostics for recursive implicits, + but the fundamental problem remains. + +## The Core Idea + +A simple principle underlies the new design: Typelevel programming in Scala 3 means reducing terms and taking their types afterwards. Specifically, if `f` is a _transparent_ function applied to some arguments `es` then the type of the application `f(es)` is the type of the term to which `f(es)` reduces. Since reduction can specialize types, that type can be more specific than `f`'s declared result type. Type-level programming in Scala 3 is thus a form of partial evaluation or [type specialization](http://www.cse.chalmers.se/~rjmh/Papers/typed-pe.html). + +## Transparent Functions + +Consider the standard definition of typelevel Peano numbers: +```scala +trait Nat +case object Z extends Nat +case class S[N <: Nat](n: N) extends Nat +``` + +A function that maps non-negative integers to Peano numbers can be defined as follows: + +```scala +transparent def toNat(n: Int): Nat = n match { + case 0 => Z + case n if n > 0 => S(toNat(n - 1)) +} +``` +Without the `transparent` modifier, an application of `toNat(3)` would have type `Nat`, as this +is the method's declared return type. But with `transparent`, the call to `toNat(3)` gets reduced _at compile time_ as follows: + + toNat(3) + -> + 3 match { + case 0 => Z + case n if n > 0 => S(toNat(n - 1)) + } + -> + S(toNat(2)) + -> + S(2 match { + case 0 => Z + case n if n > 0 => S(toNat(n - 1)) + }) + -> + S(S(toNat(1))) + -> -> + S(S(S(toNat(0)))) + -> + S(S(S(0 match { + case 0 => Z + case n if n > 0 => S(toNat(n - 1)) + }))) + -> + S(S(S(Z))) + +The type of `toNat(3)` is the type of its result, `S[S[S[Z]]]`, which is a subtype of the declared result type `Nat`. + +A `transparent` modifier on a method definition indicates that any application of the defined method outside a transparent method definition will be expanded to its right hand side, where formal parameters get bound to actual arguments. The right side will be further simplified using a set of rewrite rules given below. + +Top-level `match` expressions in transparent methods are treated specially. An expression is considered top-level if it appears + + - as the right hand side of the `transparent` function, or + - as the final expression of a top-level block, or + - as a branch of a top-level match expression. + +A top-level match expression in a transparent _must_ be rewritten at compile-time. That is, if there is enough static information to unambiguously pick one of its branches, the expression +is rewritten to that branch. Otherwise a compile-time error is signalled indicating that the match is ambiguous. If one wants to have a transparent function expand to a match expression that cannot be evaluated statically in this fashion, one can always enclose the expression in a +```scala +locally { ... } +``` +block, which de-classifies it as a top-level expression. (`locally` is a function in `Predef` which simply returns its argument.) + +As another example, consider the following two functions over tuples: + +```scala +transparent def concat(xs: Tuple, ys: Tuple): Tuple = xs match { + case () => ys + case (x, xs1) => (x, concat(xs1, ys)) +} + +transparent def nth(xs: Tuple, n: Int): Any = xs match { + case (x, _) if n == 0 => x + case (_, xs1) if n > 0 => nth(xs1, n - 1) +} +``` +Assume +```scala +as: (Int, String) +bs: (Boolean, List[Int]) +tp: Tuple +``` + +Then we get the following typings: +```scala +concat(as, bs) : (Int, String, Boolean, List[Int]) +concat(as, ()) : (Int, String) +concat((), as) : (Int, String) +concat(as, tp) : (Int, (String, Tuple)) + +nth(as, 0) : Int +nth(as, 1) : String +nth(as, 2) : Nothing +nth(as, -1) : Nothing +nth(concat(as, bs), 3) : List[Int] +``` + +In each of these cases, the result is obtained by expanding the transparent function(s), simplifying (reducing) their right hand sides, and taking the type of the result. As an example, the applications `concat(as, bs)` and `nth(as, 1)` would produce expressions like these: +```scala +concat(as, bs) --> (as._1, { val a$1 = as._2; (a$1._1, bs) } +nth(as, 1) --> { val a$1 = as._2; a$1._1 } +``` +If tuples get large, so do the expansions. For instance, the size of the expansion of a valid selection `nth(xs, n)` is proportional to `n`. We will show later a scheme to avoid this code blowup using `erased` values. + +The following expressions would all give compile-time errors since a toplevel `match` could not be reduced: +```scala +concat(tp, bs) +nth(tp, 0) +nth(as, 2) +nth(as -1) +``` +It's possible to add more cases to a toplevel match, thereby moving an error from compile-time to runtime. For instance, here is a version of `nth` that throws a runtime error in case the index is out of bounds: +```scala +transparent def nthDynamic(xs: Tuple, n: Int): Any = xs match { + case (x, _) if n == 0 => x + case (_, xs1) => nthDynamic(xs1, n - 1) + case () => throw new IndexOutOfBoundsException +} +``` +Here is an expansion of `nthDynamic` applied to a tuple `as: (Int, String)` and a negative index. For clarity we have added the computed types of the intermediate values `as$i`. +``` + nthDynamic(as, -1) + -> + { val as$0: (String, ()) = as._1 + nthDynamic(as$0, -2) + } + -> + { val as$0: (String, ()) = as._1 + { val as$1: () = as$0._1 + nthDynamic(as$1, -3) + } + } + -> + throw new IndexOutOfBoundsException +``` +So the end result of the expansion is the expression `throw new IndexOutOfBoundsException`, which has type `Nothing`. It is important to note that programs are treated as _data_ in this process, so the exception will not be thrown at compile time, but only if the program is run after it compiles without errors. + +## Rewrite Rules + +The following rewrite rules are performed when simplifying inlined bodies: + + - **Pattern matching:** Pattern matches in toplevel match expressions are evaluated and the case with the first + pattern that is statically known to match is chosen. A pattern match `E match { Case_1 ... Case_n }` is translated as follows: + + - We first create a binding `val $scrutinee_n = E` for the scrutinee of the match. + - Matching a pattern `P` takes as additional input the current scrutinee reference `S` (this is `$scrutinee_n` for toplevel patterns). It either returns with a sequence of additional bindings or fails. The rules depend on the form of the pattern `P`. + + - If `P` is a type test `Q: T`, the match fails if `S.type` does not conform to `T`. + Otherwise, proceed by matching `Q` against `S`. + - If `P` is a wildcard `_`, the match succeeds with no additional bindings. + - If `P` is some other pattern variable `x`, succeed with the additional binding `x = S`. + - If `P` is a variable binding `x @ Q`, proceed by matching `Q` against `S`. If that + succeeds, succeed with the additional binding `x = S`. + - If `P` is some non-variable reference `R`, the match succeeds with no additional bindings + if we statically know that `S` refers to the same value as `R`. + - If `P` is a constructor pattern `C(Q_1, ..., Q_n)`, where `C` refers to a case class with a compiler-generated + `unapply` method, the match fails if `S.type` does not conform to `C`. + Otherwise, proceed by matching each sub-pattern `Q_i` against the scrutinee `S._i`. If all sub-matches + succeed, collect the resulting bindings in order. + - If `P` is a pattern with a guard `Q if E`, the match succeeds if matching `Q` succeeds and + the guard `E` is statically known to be `true`. + + The first case `case P => E` with a matching pattern `P` is chosen. The match is then rewritten + to `{ Bs; E }` where `Bs` are the bindings generated by the match of `P`. + + - **Reduction of projections:** An expression `E_0(E_1, ..., E_i, ...E_n).f_i`, where + + - `E_0` is known to be - or to forward to - a class instance creation `new C`, + - `C` is known not to have initialization code, + - `f_i` is known to select the `i`'th field of `C`, + + is rewritten to `E_i`. If the prefix `E_0` or any of the other arguments `E_j` have side effects, they are + executed in order before the result of `E_i` is returned. + + - **Conditional simplification:** If the condition of an if-then-else is statically known, the conditional + is rewritten to its then- or else-part, depending on the known value of the condition. If the condition + is pure, it is dropped; otherwise the selected branch is preceded by evaluation of the condition, in order + not to drop any side effects. + + - **Reduction of function applications:** An application of a lambda `((x_1, ..., x_n) => B)(E_1, ..., E_n)` + is rewritten to + + { val/def x_1 = E_1 + ... + val/def x_n = E_n + B + } + + where `val`s are used for value parameter bindings and `def`s are used for by-name parameter bindings. + If an argument `E_i` is a simple variable reference `y`, the corresponding binding is omitted and `y` + is used instead of `x_i` in `B`. + + - **Constant folding:** If a pure expression evaluates to a constant value, it can be replaced by this value. + + - **Garbage collection:** Bindings for parameters and pattern-bound variables are dropped according to the + following rules: + + - A binding `val x = E` is dropped if `x` is not used in the rest of the expansion and `E` is pure. + - A binding `def x = E` for a by-name parameter is dropped if `x` is not used in the rest of the expansion. + - A binding `def x = E` for a by-name parameter that is used exactly once in the result of the + expansion is dropped and the reference to `x` is replaced by `E`. + + Dropping a binding might make other bindings redundant. Garbage collection proceeds until no further bindings + can be dropped. + +## Restrictions for Transparent and Typelevel Functions + +Transparent methods are effectively final; they may not be overwritten. + +If a transparent +method has a toplevel match expression or a toplevel splice `~` on its right-hand side, +it is classified as a typelevel method that can _only_ be executed at compile time. For typelevel methods two more restrictions apply: + + 1. They must be always fully applied. + 2. They may override other methods only if one of the overridden methods is concrete. + +The right hand side of a typelevel method is never invoked by dynamic dispatch. As an example consider a situation like the following: +```scala +class Iterable[T] { + def foreach(f: T => Unit): Unit = ... +} +class List[T] extends Iterable[T] { + override transparent def foreach(f: T => Unit): Unit = ... +} +val xs: Iterable[T] = ... +val ys: List[T] = ... +val zs: Iterable[T] = ys +xs.foreach(f) // calls Iterable's foreach +ys.foreach(f) // expands to the body of List's foreach +zs.foreach(f) // calls Iterable's foreach +``` +It follows that an overriding typelevel method should implement exactly the same semantics as the +method it overrides (but possibly more efficiently). + +## Matching on Types + +We have seen so far transparent functions that take terms (tuples and integers) as parameters. What if we want to base case distinctions on types instead? For instance, one would like to be able to write a function `defaultValue`, that, given a type `T` +returns optionally the default value of `T`, if it exists. In fact, we can already express +this using ordinary match expressions and a simple helper function, `scala.typelevel.erasedValue`, which is defined as follows: +```scala +erased def erasedValue[T]: T = ??? +``` +The `erasedValue` function _pretends_ to return a value of its type argument `T`. In fact, it would always raise a `NotImplementedError` exception when called. But the function can in fact never be called, since it is declared `erased`, so can be only used a compile-time during type checking. + +Using `erasedValue`, we can then define `defaultValue` as follows: +```scala +transparent def defaultValue[T] = erasedValue[T] match { + case _: Byte => Some(0: Byte) + case _: Char => Some(0: Char) + case _: Short => Some(0: Short) + case _: Int => Some(0) + case _: Long => Some(0L) + case _: Float => Some(0.0f) + case _: Double => Some(0.0d) + case _: Boolean => Some(false) + case _: Unit => Some(()) + case _: t >: Null => Some(null) + case _ => None +} +``` +Then: +```scala +defaultValue[Int] = Some(0) +defaultValue[Boolean] = Some(false) +defaultValue[String | Null] = Some(null) +defaultValue[AnyVal] = None +``` +As another example, consider the type-level inverse of `toNat`: given a _type_ representing a Peano number, return the integer _value_ corresponding to it. Here's how this can be defined: +```scala +transparent def toInt[N <: Nat]: Int = erasedValue[N] match { + case _: Z => 0 + case _: S[n] => toInt[n] + 1 +} +``` + +## Computing New Types + +The examples so far all computed _terms_ that have interesting new types. Since in Scala terms can contain types it is equally easy to compute the types of these terms directly as well. To this purpose, it is helpful +to base oneself on the helper class `scala.typelevel.Typed`, defined as follows: +```scala +class Typed[T](val value: T) { type Type = T } +``` +Here is a version of `concat` that computes at the same time a result and its type: +```scala +transparent def concatTyped(xs: Tuple, ys: Tuple): Typed[_ <: Tuple] = xs match { + case () => Typed(ys) + case (x, xs1) => Typed((x, concatTyped(xs1, ys).value)) +} +``` + +## Avoiding Code Explosion + +Recursive transparent functions implement a form of loop unrolling through inlining. This can lead to very large generated expressions. The code explosion can be avoided by calling typed versions of the transparent functions to define erased values, of which just the typed part is used afterwards. Here is how this can be done for `concat`: + +```scala +def concatImpl(xs: Tuple, ys: Tuple): Tuple = xs match { + case () => ys + case (x, xs1) => (x, concatImpl(xs1, ys)) +} + +transparent def concat(xs: Tuple, ys: Tuple): Tuple = { + erased val r = concatTyped(xs, ys) + concatImpl(xs, ys).asInstanceOf[r.Type] +} +``` +The transparent `concat` method makes use of two helper functions, `concatTyped` (described in the last section) and `concatImpl`. `concatTyped` is called as the right hand side of an `erased` value `r`. Since `r` is `erased`, no code is generated for its definition. +`concatImpl` is a regular, non-transparent function that implements `concat` on generic tuples. It is not inlineable, and its result type is always `Tuple`. The actual code for `concat` calls `concatImpl` and casts its result to type `r.Type`, the computed result type of the concatenation. This gives the best of both worlds: Compact code and expressive types. + +One might criticize that this scheme involves code duplication. In the example above, the recursive `concat` algorithm had to be implemented twice, once as a regular function, the other time as a transparent function. However, in practice it is is quite likely that the regular function would use optimized data representatons and algortihms that do not lend themselves easily to a typelevel interpretation. In these cases a dual implementation is required anyway. + +## Code Specialization + +Transparent functions are a convenient means to achieve code specialization. As an example, consider implementing a math library that implements (among others) a `dotProduct` method. Here is a possible implementation of `MathLib` with some user code. +```scala +abstract class MathLib[N : Numeric] { + def dotProduct(xs: Array[N], ys: Array[N]): N +} +object MathLib { + + transparent def apply[N](implicit n: Numeric[N]) = new MathLib[N] { + import n._ + def dotProduct(xs: Array[N], ys: Array[N]): N = { + require(xs.length == ys.length) + var i = 0 + var s: N = n.zero + while (i < xs.length) { + s = s + xs(i) * ys(i) + i += 1 + } + s + } + } +} +``` +`MathLib` is intentionally kept very abstract - it works for any element type `N` for which a `math.Numeric` implementation exists. +Here is some code that uses `MathLib`: +```scala +val mlib = MathLib[Double] + +val xs = Array(1.0, 1.0) +val ys = Array(2.0, -3.0) +mlib.dotProduct(xs, ys) +``` +The implementation code for a given numeric type `N` is produced by the `apply` method of `MathLib`. +Even though the `dotProduct` code looks simple, there is a lot of hidden complexity in the generic code: + + - It uses unbounded generic arrays, which means code on the JVM needs reflection to access their elements + - All operations on array elements forward to generic operations in class `Numeric`. + +It would be quite hard for even a good optimizer to undo the generic abstractions and replace them with something simpler if `N` is specialized to a primitive type like `Double`. But since `apply` is marked `transparent`, the specialization happens automatically as a result of inlining the body of `apply` with the new types. Indeed, the specialized version of `dotProduct` looks like this: +``` +def dotProduct(xs: Array[Double], ys: Array[Double]): Double = { + require(xs.length == ys.length) + var i = 0 + var s: Double = math.Numeric.DoubleIsFractional.zero + while (i < xs.length) { + s = s + xs(i) * ys(i) + i += 1 + } + s +} +``` +In other words, specialization with transparent functions allows "abstraction without regret". The price to pay for this +is the increase of code size through inlining. That price is often worth paying, but inlining decisions need to be considered carefully. + +## Implicit Matches + +It is foreseen that many areas of typelevel programming can be done with transparent functions instead of implicits. But sometimes implicits are unavoidable. The problem so far was that the Prolog-like programming style of implicit search becomes viral: Once some construct depends on implicit search it has to be written as a logic program itself. Consider for instance the problem +of creating a `TreeSet[T]` or a `HashSet[T]` depending on whether `T` has an `Ordering` or not. We can create a set of implicit definitions like this: +```scala +trait SetFor[T, S <: Set[T]] +class LowPriority { + implicit def hashSetFor[T]: SetFor[T, HashSet[T]] = ... +} +object SetsFor extends LowPriority { + implicit def treeSetFor[T: Ordering]: SetFor[T, TreeSet[T]] = ... +} +``` +Clearly, this is not pretty. Besides all the usual indirection of implicit search, +we face the problem of rule prioritization where +we have to ensure that `treeSetFor` takes priority over `hashSetFor` if the element type has an ordering. This is solved +(clumsily) by putting `hashSetFor` in a superclass `LowPriority` of the object `SetsFor` where `treeSetFor` is defined. +Maybe the boilerplate would still be acceptable if the crufty code could be contained. However, this is not the case. Every user of +the abstraction has to be parameterized itself with a `SetFor` implicit. Considering the simple task _"I want a `TreeSet[T]` if +`T` has an ordering and a `HashSet[T]` otherwise"_, this seems like a lot of ceremony. + +There are some proposals to improve the situation in specific areas, for instance by allowing more elaborate schemes to specify priorities. But they all keep the viral nature of implicit search programs based on logic programming. + +By contrast, the new `implicit match` construct makes implicit search available in a functional context. To solve +the problem of creating the right set, one would use it as follows: +```scala +transparent def setFor[T]: Set[T] = implicit match { + case ord: Ordering[T] => new TreeSet[T] + case _ => new HashSet[T] +} +``` +An implicit match uses the `implicit` keyword in the place of the scrutinee. Its patterns are type ascriptions of the form `identifier : Type`. + +Patterns are tried in sequence. The first case with a pattern `x: T` such that an implicit value +of type `T` can be summoned is chosen. The variable `x` is then bound to the implicit value for the remainder of the case. It can in turn be used as an implicit in the right hand side of the case. +It is an error if one of the tested patterns gives rise to an ambiguous implicit search. + +Implicit matches can only occur as toplevel match expressions of transparent methods. This ensures that +all implicit searches are done at compile-time. + +## Transparent Values + +Value definitions can also be marked `transparent`. Examples: +```scala +transparent val label = "url" +transparent val pi: Double = 3.14159265359 +``` +The right hand side of a `transparent` value definition must be a pure expression of constant type. The type of the value is then the type of its right hand side, without any widenings. For instance, the type of `label` above is the singleton type `"url"` instead of `String` and the type of `pi` is `3.14159265359` instead of `Double`. + +Transparent values are effectively final; they may not be overridden. In Scala-2, constant values had to be expressed using `final`, which gave an unfortunate double meaning to the modifier. The `final` syntax is still supported in Scala 3 for a limited time to support cross-building. + +The `transparent` modifier can also be used for value parameters of `transparent` methods. Example +```scala +transparent def power(x: Double, transparent n: Int) = ... +``` +If a `transparent` modifier is given, corresponding arguments must be pure expressions of constant type. + +However, the restrictions on right-hand sides or arguments mentioned in this section do not apply in code that is +itself in a `transparent` method. In other words, constantness checking is performed only when a `transparent` method +is expanded, not when it is defined. + +## Transparent and Inline + +Dotty up to version 0.9 also supports an `inline` modifier. `inline` is similar to `transparent` in that inlining happens during type-checking. However, `inline` does not change the types of the inlined expressions. The expressions are instead inlined in the form of trees that are already typed. + +Since there is very large overlap between `transparent` and `inline`, we propose to drop `inline` as a separate modifier. The current `@inline` annotation, which represents a hint to the optimizer that inlining might be advisable, will remain unaffected. + +## Relationship to "TypeOf" + +This document describes one particular way to conceptualize and implement transparent methods. An implementation is under way in #4616. An alternative approach is explored in #4671. The main difference is that the present proposal uses the machinery of partial evaluation (PE) whereas #4671 uses the machinery of (term-) dependent types (DT). + +The PE approach reduces the original right-hand side of a transparent function via term rewriting. The right hand side is conceptually the term as it is written down, closed over the environment where the transparent function was defined. This is implemented by rewriting and then re-type-checking the original untyped tree, but with typed leaves that refer to the tree's free variables. The re-type-checking with more specific argument types can lead to a type derivation that is quite different +than the original typing of the transparent method's right hand side. Types might be inferred to be more specific, overloading resolution could pick different alternatives, and implicit searches might yield different results or might be elided altogether. +This flexibility is what makes code specialization work smoothly. + +By constrast, the DT approach typechecks the right hand side of a transparent function in a special way. Instead of +computing types as usual, it "lifts" every term into a type that mirrors it. To do this, it needs to create type equivalents +of all relevant term forms including applications, conditionals, match expressions, and the various forms of pattern matches. +The end result of the DT approach is then a type `{t}` that represents essentially the right-hand side term `t` itself, or at least all portions that might contribute to that type. The type has to be constructed in such a way that the result type of every application +of a transparent function can be read off from it. +An application of a transparent function then needs to just instantiate that type, whereas the term is not affected at all, and the function is called as usual. This means there is a guarantee that the +semantics of a transparent call is the same as a normal call, only the type is better. On the other hand, one cannot play +tricks such as code specialization, since there is nothing to specialize. +Also, implicit matches would not fit into this scheme, as they require term rewritings. Another concern is how much additional complexity would be caused by mirroring the necessary term forms in types and tweaking type inference to work with the new types. + +To summarize, here are some advantages of DT over PE: + + + It avoids code explosion by design (can be solved in PE but requires extra work). + + Expansion only works on types, so this might well be faster at compile-time than PE's term rewriting. + + It gives a guarantee that `transparent` is semantics preserving. + + The typeof `{t}` operator is interesting in its own right. + +By contrast, here are some advantages of PE over DT: + + + It uses the standard type checker and typing rules with no need to go to full dependent types + (only path dependent types instead of full term-dependencies). + + It can do code specialization. + + It can do implicit matches. + + It can also serve as the inlining mechanism for implementing macros with staging (see next section). + +## Relationship To Meta Programming + +Transparent functions are a safer alternative to the whitebox macros in Scala 2. Both share the principle that some function applications get expanded such that the types of these applications are the types of their expansions. But the way the expansions are performed is completely different: For transparent functions, expansions are simply beta reduction (_aka_ inlining) and a set of rewrite rules that are sound wrt the language semantics. All rewritings are performed by the compiler itself. It is really as if some parts of the program reduction are performed early, which is the essence of partial evaluation. + +By contrast, Scala 2 macros mean that user-defined code is invoked to process program fragments as data. The result of this computation is then embedded instead of the macro call. Macros are thus a lot more powerful than transparent functions, but also a lot less safe. + +Functionality analogous to blackbox macros in Scala-2 is available in Scala-3 through its [principled meta programming](https://dotty.epfl.ch/docs/reference/principled-meta-programming.html) system: Code can be turned into data using quotes `(')`. Code-returning computations can be inserted into quotes using splices `(~)`. A splice outside quotes means that the spliced computation is _run_, which is the analogue of +invoking a macro. Quoted code can be inspected using reflection on Tasty trees. + +To compare: here's the scheme used in Scala-2 to define a macro: +```scala +def m(x: T) = macro mImpl +... +def mImpl(x: Tree) = ... +``` +Here's analogous functionality in Scala-3: +```scala +transparent def m(x: T) = ~mImpl('x) +... +def mImpl(x: Expr[T]) = ... +``` +The new scheme is more explicit and orthogonal: The usual way to define a macro is as a transparent function that invokes with `~` a macro library method, passing it quoted arguments as data. The role of the transparent function is simply to avoid to have to write the splices and quotes in user code. + +One important difference between the two schemes is that the reflective call implied by `~` is performed _after_ the program is typechecked. Besides a better separation of concerns, this also provides a more predictable environment for macro code to run in. + +## Acknowledgments + +Many of the ideas in this proposal resulted from discussions with @gsps and @OlivierBlanvillain, the authors of the "TypeOf" approach (PR #4671). @gsps suggested the use of the `transparent` keyword. @OlivierBlanvillain suggested techniques like `erasedValue` and `Typed` to lift term computations to types. The present proposal also benefited from feedback from @milessabin, @adriaanm, @sjrd, Andrei Alexandrescu, John Hughes, Conor McBride and Stephanie Weirich on earlier designs. The relationship with meta programming has a lot in common with the original inline and meta proposals in SIP 28 and SIP 29. diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 8b448fb7ac01..d6d7cc681dfc 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -43,7 +43,7 @@ sidebar: url: docs/reference/multiversal-equality.html - title: Trait Parameters url: docs/reference/trait-parameters.html - - title: Inline + - title: Inlining with Transparent url: docs/reference/inline.html - title: Meta Programming url: docs/reference/principled-meta-programming.html diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index 632604e4691d..b831f1c3f6dc 100644 --- a/library/src/dotty/DottyPredef.scala +++ b/library/src/dotty/DottyPredef.scala @@ -34,8 +34,10 @@ object DottyPredef { assertFail() } - final def assertFail(): Unit = throw new java.lang.AssertionError("assertion failed") - final def assertFail(message: => Any): Unit = throw new java.lang.AssertionError("assertion failed: " + message) + def assertFail(): Unit = throw new java.lang.AssertionError("assertion failed") + def assertFail(message: => Any): Unit = throw new java.lang.AssertionError("assertion failed: " + message) @forceInline final def implicitly[T](implicit ev: T): T = ev + + @forceInline def locally[T](body: => T): T = body } diff --git a/library/src/scala/annotation/internal/Body.scala b/library/src/scala/annotation/internal/Body.scala index b6aa0c0fb616..abc5ba1b7682 100644 --- a/library/src/scala/annotation/internal/Body.scala +++ b/library/src/scala/annotation/internal/Body.scala @@ -3,6 +3,6 @@ package scala.annotation.internal import scala.annotation.Annotation /** The class associated with a `BodyAnnotation`, which indicates - * an inline method's right hand side + * a transparent method's right hand side */ final class Body() extends Annotation diff --git a/library/src/scala/annotation/internal/InlineParam.scala b/library/src/scala/annotation/internal/InlineParam.scala deleted file mode 100644 index 0b3649e89da9..000000000000 --- a/library/src/scala/annotation/internal/InlineParam.scala +++ /dev/null @@ -1,6 +0,0 @@ -package scala.annotation.internal - -import scala.annotation.Annotation - -/** An annotation produced by Namer to indicate an inline parameter */ -final class InlineParam() extends Annotation diff --git a/library/src/scala/annotation/internal/TransparentParam.scala b/library/src/scala/annotation/internal/TransparentParam.scala new file mode 100644 index 000000000000..1560fa073449 --- /dev/null +++ b/library/src/scala/annotation/internal/TransparentParam.scala @@ -0,0 +1,6 @@ +package scala.annotation.internal + +import scala.annotation.Annotation + +/** An annotation produced by Namer to indicate a transparent parameter */ +final class TransparentParam() extends Annotation diff --git a/library/src/scala/quoted/Expr.scala b/library/src/scala/quoted/Expr.scala index 4c87ca4a5b7f..12ef1915e933 100644 --- a/library/src/scala/quoted/Expr.scala +++ b/library/src/scala/quoted/Expr.scala @@ -7,7 +7,7 @@ sealed abstract class Expr[T] { /** Evaluate the contents of this expression and return the result. * - * May throw a FreeVariableError on expressions that came from an inline macro. + * May throw a FreeVariableError on expressions that came from a transparent macro. */ final def run(implicit toolbox: Toolbox): T = toolbox.run(this) @@ -44,7 +44,7 @@ object Exprs { /** An Expr backed by a tree. Only the current compiler trees are allowed. * - * These expressions are used for arguments of inline macros. They contain and actual tree + * These expressions are used for arguments of transparent macros. They contain and actual tree * from the program that is being expanded by the macro. * * May contain references to code defined outside this TastyTreeExpr instance. diff --git a/library/src/scala/quoted/QuoteError.scala b/library/src/scala/quoted/QuoteError.scala index 8db441d90b04..1b800240fc44 100644 --- a/library/src/scala/quoted/QuoteError.scala +++ b/library/src/scala/quoted/QuoteError.scala @@ -1,6 +1,6 @@ package scala.quoted -/** Throwing this error in the implementation of an inline macro +/** Throwing this error in the implementation of a transparent macro * will result in a compilation error with the given message. */ class QuoteError(message: String) extends Throwable(message) diff --git a/library/src/scala/tasty/FlagSet.scala b/library/src/scala/tasty/FlagSet.scala index 571a0ffa564b..0acfffa3bfc6 100644 --- a/library/src/scala/tasty/FlagSet.scala +++ b/library/src/scala/tasty/FlagSet.scala @@ -10,8 +10,8 @@ trait FlagSet { def isErased: Boolean def isLazy: Boolean def isOverride: Boolean - def isInline: Boolean - def isMacro: Boolean // inline method containing toplevel splices + def isTransparent: Boolean + def isMacro: Boolean // transparent method containing toplevel splices def isStatic: Boolean // mapped to static Java member def isObject: Boolean // an object or its class (used for a ValDef or a ClassDef extends Modifier respectively) def isTrait: Boolean // a trait (used for a ClassDef) diff --git a/library/src/scala/tasty/TopLevelSplice.scala b/library/src/scala/tasty/TopLevelSplice.scala index 9d5392860bd7..034f211c0c2a 100644 --- a/library/src/scala/tasty/TopLevelSplice.scala +++ b/library/src/scala/tasty/TopLevelSplice.scala @@ -3,5 +3,5 @@ package scala.tasty /** Context in a top level ~ at inline site, intrinsically as `import TopLevelSplice._` */ object TopLevelSplice { /** Compiler tasty context available in a top level ~ at inline site */ - implicit def tastyContext: Tasty = throw new Exception("Not in inline macro.") + implicit def tastyContext: Tasty = throw new Exception("Not in transparent macro.") } diff --git a/library/src/scala/tasty/util/ShowSourceCode.scala b/library/src/scala/tasty/util/ShowSourceCode.scala index 7d3d7bd479a4..71251847299f 100644 --- a/library/src/scala/tasty/util/ShowSourceCode.scala +++ b/library/src/scala/tasty/util/ShowSourceCode.scala @@ -247,7 +247,7 @@ class ShowSourceCode[T <: Tasty with Singleton](tasty0: T) extends Show[T](tasty val flags = ddef.flags if (flags.isImplicit) this += "implicit " - if (flags.isInline) this += "inline " + if (flags.isTransparent) this += "transparent " if (flags.isOverride) this += "override " printProtectedOrPrivate(ddef) @@ -384,7 +384,7 @@ class ShowSourceCode[T <: Tasty with Singleton](tasty0: T) extends Show[T](tasty this += lineBreak() += "}" } - case Term.Inlined(call, bindings, expansion) => + case Term.Inlined(call, bindings, expansion) => // FIXME: Don't print Inlined with empty calls? this += "{ // inlined" indented { printStats(bindings, expansion) diff --git a/sbt-dotty/sbt-test/source-dependencies/inline/changes/B1.scala b/sbt-dotty/sbt-test/source-dependencies/inline/changes/B1.scala index fc714b93b0a9..1032753814f2 100644 --- a/sbt-dotty/sbt-test/source-dependencies/inline/changes/B1.scala +++ b/sbt-dotty/sbt-test/source-dependencies/inline/changes/B1.scala @@ -1,4 +1,4 @@ object B { - inline def getInline: Int = + transparent def getInline: Int = A.get } diff --git a/sbt-dotty/sbt-test/source-dependencies/inline/changes/B2.scala b/sbt-dotty/sbt-test/source-dependencies/inline/changes/B2.scala index 9a3abb0d9792..ca7395adf16d 100644 --- a/sbt-dotty/sbt-test/source-dependencies/inline/changes/B2.scala +++ b/sbt-dotty/sbt-test/source-dependencies/inline/changes/B2.scala @@ -1,4 +1,4 @@ object B { - inline def getInline: Double = + transparent def getInline: Double = A.get } diff --git a/sbt-dotty/sbt-test/source-dependencies/inline/changes/B3.scala b/sbt-dotty/sbt-test/source-dependencies/inline/changes/B3.scala index 2fffbde1bbf9..a121f6672300 100644 --- a/sbt-dotty/sbt-test/source-dependencies/inline/changes/B3.scala +++ b/sbt-dotty/sbt-test/source-dependencies/inline/changes/B3.scala @@ -1,4 +1,4 @@ object B { - inline def getInline: Int = + transparent def getInline: Int = sys.error("This is an expected failure when running C") } diff --git a/tests/neg/erased-4.scala b/tests/neg/erased-4.scala index 99a09de2c404..2c60ad303dae 100644 --- a/tests/neg/erased-4.scala +++ b/tests/neg/erased-4.scala @@ -1,6 +1,8 @@ object Test { def main(args: Array[String]): Unit = { + def foo (erased i: Int) = 0 + val f: erased Int => Int = erased (x: Int) => { x // error @@ -10,8 +12,6 @@ object Test { erased (x: Int) => { foo(x) } - - def foo (erased i: Int) = 0 } } diff --git a/tests/neg/erased-case-class.scala b/tests/neg/erased-case-class.scala index d23a09ba24f0..692534d772b6 100644 --- a/tests/neg/erased-case-class.scala +++ b/tests/neg/erased-case-class.scala @@ -1 +1 @@ -case class Foo1(erased x: Int) // error +case class Foo1(erased x: Int) // error // error diff --git a/tests/neg/i1568.scala b/tests/neg/i1568.scala index a260c530b927..2e3b2dae2685 100644 --- a/tests/neg/i1568.scala +++ b/tests/neg/i1568.scala @@ -1,3 +1,3 @@ object Test { - inline def foo(n: Int) = foo(n) // error: cyclic reference + transparent def foo(n: Int) = foo(n) // error: cyclic reference } diff --git a/tests/neg/i1605.scala b/tests/neg/i1605.scala index f5fd9f29bde5..0515ec751df0 100644 --- a/tests/neg/i1605.scala +++ b/tests/neg/i1605.scala @@ -1,5 +1,5 @@ object Test { def foo = inlineMe - inline def inlineMe = 1 + x2233 // error + transparent def inlineMe = 1 + x2233 // error } diff --git a/tests/neg/i2006.scala b/tests/neg/i2006.scala index f1b48b011384..910d5fc1e2f6 100644 --- a/tests/neg/i2006.scala +++ b/tests/neg/i2006.scala @@ -1,7 +1,7 @@ object Test { - inline def foo(f: ImplicitFunction1[Int, Int]): AnyRef = f // error - inline def bar(f: ImplicitFunction1[Int, Int]) = f // error + transparent def foo(f: ImplicitFunction1[Int, Int]): AnyRef = f // error + transparent def bar(f: ImplicitFunction1[Int, Int]) = f // error def main(args: Array[String]) = { foo(implicit thisTransaction => 43) diff --git a/tests/neg/i2421.scala b/tests/neg/i2421.scala index 759145119e7b..ff2ef194b430 100644 --- a/tests/neg/i2421.scala +++ b/tests/neg/i2421.scala @@ -1,10 +1,10 @@ -inline object Foo // OK (error would be detected later, in PostTyper) -inline class Bar // error: modifier(s) `inline' incompatible with type definition -inline abstract class Baz // error: modifier(s) `inline' incompatible with type definition -inline trait Qux // error: modifier(s) `inline' incompatible with type definition +transparent object Foo // OK (error would be detected later, in PostTyper) +transparent class Bar // error: modifier(s) `transparent' incompatible with type definition +transparent abstract class Baz // error: modifier(s) `transparent' incompatible with type definition +transparent trait Qux // error: modifier(s) `transparent' incompatible with type definition object Quux { - inline type T // error: modifier(s) `inline' incompatible with type definition - inline var x: Int = 42 // error: modifier(s) `inline' incompatible with var definition - inline lazy val y: Int = 43 // error: modifier(s) `inline' incompatible with lazy val definition + transparent type T // error: modifier(s) `transparent' incompatible with type definition + transparent var x: Int = 42 // error: modifier(s) `transparent' incompatible with var definition + transparent lazy val y: Int = 43 // error: modifier(s) `transparent' incompatible with lazy val definition } diff --git a/tests/neg/i2564.scala b/tests/neg/i2564.scala index 4d8072f99297..f0f63dc4b642 100644 --- a/tests/neg/i2564.scala +++ b/tests/neg/i2564.scala @@ -1,4 +1,4 @@ object Foo { - inline def bar = new Bar // error + transparent def bar = new Bar // error class Bar private[Foo]() } diff --git a/tests/neg/i2564b.scala b/tests/neg/i2564b.scala index 508f0ed9a8b8..48e230654bb8 100644 --- a/tests/neg/i2564b.scala +++ b/tests/neg/i2564b.scala @@ -1,3 +1,3 @@ class Foo private() { - inline def foo = new Foo // error + transparent def foo = new Foo // error } diff --git a/tests/neg/i2901.scala b/tests/neg/i2901.scala index 9c9ff616d205..a1f13dfa4ee1 100644 --- a/tests/neg/i2901.scala +++ b/tests/neg/i2901.scala @@ -2,7 +2,7 @@ trait Foo { def foo = 4 } object Bar extends Foo { - inline def bar = super[Foo].foo // error + transparent def bar = super[Foo].foo // error } object Main { Bar.bar diff --git a/tests/neg/i3900.scala b/tests/neg/i3900.scala index c64bd6bca71b..7439610e9696 100644 --- a/tests/neg/i3900.scala +++ b/tests/neg/i3900.scala @@ -1,7 +1,7 @@ -class Foo(inline val i: Int) // error -case class Foo2(inline val i: Int) // error -class Foo3(inline val i: Int) extends AnyVal // error -trait Foo4(inline val i: Int) // error +class Foo(transparent val i: Int) // error +case class Foo2(transparent val i: Int) // error +class Foo3(transparent val i: Int) extends AnyVal // error +trait Foo4(transparent val i: Int) // error class Foo5() { - def this(inline x: Int) = this() // error + def this(transparent x: Int) = this() // error } diff --git a/tests/neg/i4433.scala b/tests/neg/i4433.scala index 49237a6e8ac5..4f8226f55e64 100644 --- a/tests/neg/i4433.scala +++ b/tests/neg/i4433.scala @@ -1,6 +1,6 @@ object Foo { - inline def g(inline p: Int => Boolean): Boolean = ~{ + transparent def g(transparent p: Int => Boolean): Boolean = ~{ if(p(5)) '(true) // error else '(false) } diff --git a/tests/neg/implicitMatch-ambiguous.scala b/tests/neg/implicitMatch-ambiguous.scala new file mode 100644 index 000000000000..c7d485b8a29b --- /dev/null +++ b/tests/neg/implicitMatch-ambiguous.scala @@ -0,0 +1,12 @@ +object Test { + + class A + implicit val a1: A = new A + implicit val a2: A = new A + + transparent def f: Any = implicit match { + case _: A => ??? // error: ambiguous implicits + } + + f +} \ No newline at end of file diff --git a/tests/neg/implicitMatch-syntax.scala b/tests/neg/implicitMatch-syntax.scala new file mode 100644 index 000000000000..97a47f20f66b --- /dev/null +++ b/tests/neg/implicitMatch-syntax.scala @@ -0,0 +1,33 @@ +object Test { + import collection.immutable.TreeSet + import collection.immutable.HashSet + + transparent def f1[T] = implicit implicit match { // error: repeated modifier // error: illegal modifier + case ord: Ordered[T] => new TreeSet[T] // error: no implicit + case _ => new HashSet[T] + + } + + transparent def f2[T] = implicit erased match { // error: illegal modifier + case ord: Ordered[T] => new TreeSet[T] // error: no implicit + case _ => new HashSet[T] + } + + transparent def f3[T] = erased implicit match { // error: illegal modifier + case ord: Ordered[T] => new TreeSet[T] // error: no implicit + case _ => new HashSet[T] + } + + transparent def f4() = implicit match { + case Nil => ??? // error: not a legal pattern + case x :: xs => ??? // error: not a legal pattern + } + + transparent def f5[T] = locally { implicit match { // error: implicit match cannot be used here + case _ => new HashSet[T] + }} + + def f6[T] = implicit match { // error: implicit match cannot be used here + case _ => new HashSet[T] + } +} \ No newline at end of file diff --git a/tests/pos/inline-i1773.scala b/tests/neg/inline-i1773.scala similarity index 58% rename from tests/pos/inline-i1773.scala rename to tests/neg/inline-i1773.scala index b24f8c34d2e1..9e377b23213f 100644 --- a/tests/pos/inline-i1773.scala +++ b/tests/neg/inline-i1773.scala @@ -1,13 +1,13 @@ object Test { implicit class Foo(sc: StringContext) { object q { - inline def unapply(arg: Any): Option[(Any, Any)] = + transparent def unapply(arg: Any): Option[(Any, Any)] = Some((sc.parts(0), sc.parts(1))) } } def main(args: Array[String]): Unit = { - val q"class $name extends $parent" = new Object + val q"class $name extends $parent" = new Object // error: method unapply is used println(name) println(parent) } diff --git a/tests/neg/inline-position/A_1.scala b/tests/neg/inline-position/A_1.scala new file mode 100644 index 000000000000..b031a5df69cd --- /dev/null +++ b/tests/neg/inline-position/A_1.scala @@ -0,0 +1,5 @@ +object A { + + transparent def f(x: => Object) = (x, x) + +} \ No newline at end of file diff --git a/tests/neg/inline-position/B_2.scala b/tests/neg/inline-position/B_2.scala new file mode 100644 index 000000000000..c3292081cbfc --- /dev/null +++ b/tests/neg/inline-position/B_2.scala @@ -0,0 +1,5 @@ +object B { + erased val y = Array('a') + + A.f(new String(y)) // error: value y is declared as erased but is in fact used +} \ No newline at end of file diff --git a/tests/neg/inlineAccess/C_1.scala b/tests/neg/inlineAccess/C_1.scala index 9d34fa3f0f1a..f35ef60c88d3 100644 --- a/tests/neg/inlineAccess/C_1.scala +++ b/tests/neg/inlineAccess/C_1.scala @@ -1,7 +1,7 @@ package p private class D class C { - inline def inl(): Unit = { + transparent def inl(): Unit = { val d = new D() // error (when inlined): not accessible } } diff --git a/tests/neg/inlinevals.scala b/tests/neg/inlinevals.scala index 6c8c4eaf6327..c754991e567c 100644 --- a/tests/neg/inlinevals.scala +++ b/tests/neg/inlinevals.scala @@ -1,30 +1,35 @@ object Test { - def power(x: Double, inline n: Int): Double = ??? + def power0(x: Double, transparent n: Int): Double = ??? // error - inline val N = 10 + transparent def power(x: Double, transparent n: Int): Double = ??? // ok + + transparent val N = 10 def X = 20 - inline inline val twice = 30 // error: repeated modifier + transparent transparent val twice = 30 // error: repeated modifier - class C(inline x: Int, private inline val y: Int) { // error // error - inline val foo: Int // error: abstract member may not be inline - inline def bar: Int // error: abstract member may not be inline + class C(transparent x: Int, private transparent val y: Int) { // error // error + transparent val foo: Int // error: abstract member may not be inline + transparent def bar: Int // error: abstract member may not be inline } power(2.0, N) // ok, since it's a by-name parameter - power(2.0, X) // error: argument to inline parameter must be a constant expression + power(2.0, X) // error: argument to transparent parameter must be a constant expression + + transparent val M = X // error: rhs must be constant expression - inline val M = X // error: rhs must be constant expression + transparent val xs = List(1, 2, 3) // error: must be a constant expression - inline val xs = List(1, 2, 3) // error: must be a constant expression + transparent def foo(x: Int) = { - def f(inline xs: List[Int]) = xs + def f(transparent xs: List[Int]) = xs // error - f(List(1, 2, 3)) // error: must be a constant expression + transparent val y = { println("hi"); 1 } // ok + transparent val z = x // ok - def byname(inline f: => String): Int = ??? // ok + } - byname("hello" ++ " world") + transparent def byname(transparent f: => String): Int = ??? // ok } diff --git a/tests/neg/power.scala b/tests/neg/power.scala index d5098fc215ed..8b17717495a1 100644 --- a/tests/neg/power.scala +++ b/tests/neg/power.scala @@ -1,14 +1,14 @@ object Test { - inline def power(x: Double, n: Int): Double = + transparent def power(x: Double, n: Int): Double = if (n == 0) 1.0 else if (n == 1) x else { - val y = power(x, n / 2) // error: maximal number of inlines exceeded + val y = power(x, n / 2) if (n % 2 == 0) y * y else y * y * x } def main(args: Array[String]): Unit = { - println(power(2.0, args.length)) + println(power(2.0, args.length)) // error: maximal number of inlines exceeded } } diff --git a/tests/neg/quote-MacroOverride.scala b/tests/neg/quote-MacroOverride.scala index cfc0a7ebb374..8863ba82191e 100644 --- a/tests/neg/quote-MacroOverride.scala +++ b/tests/neg/quote-MacroOverride.scala @@ -2,11 +2,11 @@ object Test { abstract class A { def f(): Unit - inline def g(): Unit = () + transparent def g(): Unit = () } object B extends A { - inline def f() = ~('()) // error: may not override + transparent def f() = ~('()) // error: may not override override def g() = () // error: may not override } diff --git a/tests/neg/quote-error-2/Macro_1.scala b/tests/neg/quote-error-2/Macro_1.scala index 0cce8284f871..dd65339495db 100644 --- a/tests/neg/quote-error-2/Macro_1.scala +++ b/tests/neg/quote-error-2/Macro_1.scala @@ -1,7 +1,7 @@ import quoted._ object Macro_1 { - inline def foo(inline b: Boolean): Unit = ~fooImpl(b) + transparent def foo(transparent b: Boolean): Unit = ~fooImpl(b) def fooImpl(b: Boolean): Expr[Unit] = '(println(~msg(b))) diff --git a/tests/neg/quote-error/Macro_1.scala b/tests/neg/quote-error/Macro_1.scala index d6fbeb9a9ed9..aae163486c95 100644 --- a/tests/neg/quote-error/Macro_1.scala +++ b/tests/neg/quote-error/Macro_1.scala @@ -1,7 +1,7 @@ import quoted._ object Macro_1 { - inline def foo(inline b: Boolean): Unit = ~fooImpl(b) + transparent def foo(transparent b: Boolean): Unit = ~fooImpl(b) def fooImpl(b: Boolean): Expr[Unit] = if (b) '(println("foo(true)")) else QuoteError("foo cannot be called with false") diff --git a/tests/neg/quote-exception/Macro_1.scala b/tests/neg/quote-exception/Macro_1.scala index 256f482db18c..33e9eab33715 100644 --- a/tests/neg/quote-exception/Macro_1.scala +++ b/tests/neg/quote-exception/Macro_1.scala @@ -1,7 +1,7 @@ import quoted._ object Macro_1 { - inline def foo(inline b: Boolean): Unit = ~fooImpl(b) + transparent def foo(transparent b: Boolean): Unit = ~fooImpl(b) def fooImpl(b: Boolean): Expr[Unit] = if (b) '(println("foo(true)")) else ??? diff --git a/tests/neg/quote-interpolator-core-old.scala b/tests/neg/quote-interpolator-core-old.scala index 43eeef3ff95f..5ad1f7a87e57 100644 --- a/tests/neg/quote-interpolator-core-old.scala +++ b/tests/neg/quote-interpolator-core-old.scala @@ -5,9 +5,9 @@ import scala.quoted._ object FInterpolation { implicit class FInterpolatorHelper(val sc: StringContext) extends AnyVal { - inline def ff(arg1: Any): String = ~fInterpolation(sc, Seq('(arg1))) // error: Inline macro method must be a static method - inline def ff(arg1: Any, arg2: Any): String = ~fInterpolation(sc, Seq('(arg1), '(arg2))) // error: Inline macro method must be a static method - inline def ff(arg1: Any, arg2: Any, arg3: Any): String = ~fInterpolation(sc, Seq('(arg1), '(arg2), '(arg3))) // error: Inline macro method must be a static method + transparent def ff(arg1: Any): String = ~fInterpolation(sc, Seq('(arg1))) // error: Inline macro method must be a static method + transparent def ff(arg1: Any, arg2: Any): String = ~fInterpolation(sc, Seq('(arg1), '(arg2))) // error: Inline macro method must be a static method + transparent def ff(arg1: Any, arg2: Any, arg3: Any): String = ~fInterpolation(sc, Seq('(arg1), '(arg2), '(arg3))) // error: Inline macro method must be a static method // ... } diff --git a/tests/neg/quote-macro-splice.scala b/tests/neg/quote-macro-splice.scala index e854a76a77f7..52c7042c32d0 100644 --- a/tests/neg/quote-macro-splice.scala +++ b/tests/neg/quote-macro-splice.scala @@ -2,22 +2,22 @@ import scala.quoted._ object Test { - inline def foo1: Int = { // error + transparent def foo1: Int = { // error println() ~impl(1.toExpr) } - inline def foo2: Int = { // error + transparent def foo2: Int = { // error ~impl(1.toExpr) ~impl(2.toExpr) } - inline def foo3: Int = { // error + transparent def foo3: Int = { // error val a = 1 ~impl('(a)) } - inline def foo4: Int = { // error + transparent def foo4: Int = { // error ~impl('(1)) 1 } diff --git a/tests/neg/quote-non-static-macro.scala b/tests/neg/quote-non-static-macro.scala index bf90545bb3b4..32600eae7bb7 100644 --- a/tests/neg/quote-non-static-macro.scala +++ b/tests/neg/quote-non-static-macro.scala @@ -1,18 +1,18 @@ import scala.quoted._ class Foo { - inline def foo: Unit = ~Foo.impl // error + transparent def foo: Unit = ~Foo.impl // error object Bar { - inline def foo: Unit = ~Foo.impl // error + transparent def foo: Unit = ~Foo.impl // error } } object Foo { class Baz { - inline def foo: Unit = ~impl // error + transparent def foo: Unit = ~impl // error } object Quox { - inline def foo: Unit = ~Foo.impl + transparent def foo: Unit = ~Foo.impl } def impl: Expr[Unit] = '() } diff --git a/tests/neg/quote-run-in-macro-1/quoted_1.scala b/tests/neg/quote-run-in-macro-1/quoted_1.scala index f54a4fe00888..aa9191e1167f 100644 --- a/tests/neg/quote-run-in-macro-1/quoted_1.scala +++ b/tests/neg/quote-run-in-macro-1/quoted_1.scala @@ -3,7 +3,7 @@ import scala.quoted._ import dotty.tools.dotc.quoted.Toolbox._ object Macros { - inline def foo(i: => Int): Int = ~{ + transparent def foo(i: => Int): Int = ~{ val y: Int = ('(i)).run y.toExpr } diff --git a/tests/neg/quote-run-in-macro-2/quoted_1.scala b/tests/neg/quote-run-in-macro-2/quoted_1.scala index f54a4fe00888..aa9191e1167f 100644 --- a/tests/neg/quote-run-in-macro-2/quoted_1.scala +++ b/tests/neg/quote-run-in-macro-2/quoted_1.scala @@ -3,7 +3,7 @@ import scala.quoted._ import dotty.tools.dotc.quoted.Toolbox._ object Macros { - inline def foo(i: => Int): Int = ~{ + transparent def foo(i: => Int): Int = ~{ val y: Int = ('(i)).run y.toExpr } diff --git a/tests/neg/shapeless-hcons.scala b/tests/neg/shapeless-hcons.scala new file mode 100644 index 000000000000..a38effea5d13 --- /dev/null +++ b/tests/neg/shapeless-hcons.scala @@ -0,0 +1,38 @@ +package shapeless { + +sealed trait HList extends Product with Serializable { + def :: (x: Any): HList = new ::(x, this) +} + +final case class ::[+H, +T <: HList](head : H, tail : T) extends HList { + override def toString = head match { + case _: ::[_, _] => s"($head) :: $tail" + case _ => s"$head :: $tail" + } +} + +sealed trait HNil extends HList { + override def toString = "HNil" +} + +case object HNil extends HNil + +} +import shapeless._ + +package test { + +object Test { + + val xs = 1 :: 2 :: Nil + val ys = (3, 4) + + (xs: Any) match { + case x :: xs => ??? + } + xs match { + case x :: xs => ??? // error: unreachable case + } + +} +} \ No newline at end of file diff --git a/tests/neg/tasty-macro-assert/quoted_1.scala b/tests/neg/tasty-macro-assert/quoted_1.scala index 66076341fc2d..8900e1538fbe 100644 --- a/tests/neg/tasty-macro-assert/quoted_1.scala +++ b/tests/neg/tasty-macro-assert/quoted_1.scala @@ -11,7 +11,7 @@ object Asserts { object Ops - inline def macroAssert(cond: Boolean): Unit = + transparent def macroAssert(cond: Boolean): Unit = ~impl('(cond))(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl(cond: Expr[Boolean])(implicit tasty: Tasty): Expr[Unit] = { diff --git a/tests/neg/transparent-override/A_1.scala b/tests/neg/transparent-override/A_1.scala new file mode 100644 index 000000000000..d80fc2a5462d --- /dev/null +++ b/tests/neg/transparent-override/A_1.scala @@ -0,0 +1,7 @@ +abstract class A { + def f(x: Int): Int + transparent def g(x: Int): Int = x +} + + + diff --git a/tests/neg/transparent-override/B_2.scala b/tests/neg/transparent-override/B_2.scala new file mode 100644 index 000000000000..c4613f91e9e8 --- /dev/null +++ b/tests/neg/transparent-override/B_2.scala @@ -0,0 +1,9 @@ +class B extends A { + transparent def f(x: Int): Int = x match { // error + case 0 => 1 + case _ => x + } + def g(x: Int): Int = 1 // error +} + + diff --git a/tests/neg/typelevel-erased-leak.scala b/tests/neg/typelevel-erased-leak.scala new file mode 100644 index 000000000000..e55a47486d89 --- /dev/null +++ b/tests/neg/typelevel-erased-leak.scala @@ -0,0 +1,15 @@ + +object typelevel { + erased def erasedValue[T]: T = ??? +} + +object Test { + + transparent def test[T] = typelevel.erasedValue[T] match { // error + case b: Byte => b + case c: Char => "A" + } + + test[Byte] + test[Char] // ok +} \ No newline at end of file diff --git a/tests/neg/typelevel-noeta.scala b/tests/neg/typelevel-noeta.scala new file mode 100644 index 000000000000..c25cd6971b6b --- /dev/null +++ b/tests/neg/typelevel-noeta.scala @@ -0,0 +1,28 @@ + +object Test { + def anyValue[T]: T = ??? + + transparent def test(x: Int) = x match { + case _: Byte => + case _: Char => + } + + transparent def test2() = 1 match { + case _: Byte => + case _: Char => + } + + erased def test3(x: Int) = x + 1 + + def f(g: Int => Int) = g(0) + + f(test3) // OK, so far, normal erased functions can be eta-expanded + + val x: Any = test // error + + test // error + + val x2: Any = test2 // error + + test2 // error +} \ No newline at end of file diff --git a/tests/neg/typelevel-nomatch.scala b/tests/neg/typelevel-nomatch.scala new file mode 100644 index 000000000000..a4dc222336f5 --- /dev/null +++ b/tests/neg/typelevel-nomatch.scala @@ -0,0 +1,13 @@ + +object Test { + def anyValue[T]: T = ??? + + transparent def test[T] = anyValue[T] match { // error + case _: Byte => + case _: Char => + } + + test[String] + + test +} \ No newline at end of file diff --git a/tests/neg/typelevel.scala b/tests/neg/typelevel.scala new file mode 100644 index 000000000000..201722ded1d9 --- /dev/null +++ b/tests/neg/typelevel.scala @@ -0,0 +1,58 @@ +trait HList { + def length: Int + def head: Any + def tail: HList + transparent def isEmpty: Boolean = + length == 0 +} + +// () +case object HNil extends HList { + transparent def length = 0 + def head: Nothing = ??? + def tail: Nothing = ??? +} + +// (H, T) +case class HCons[H, T <: HList](hd: H, tl: T) extends HList { + transparent def length = 1 + tl.length + def head: H = this.hd + def tail: T = this.tl +} + +object Test { + transparent def concat(xs: HList, ys: HList): HList = + if xs.isEmpty then ys + else HCons(xs.head, concat(xs.tail, ys)) + + class Deco(private val as: HList) { + transparent def ++ (bs: HList) = concat(as, bs) + } + + class Deco0(val as: HList) { + println("HI") + transparent def ++ (bs: HList) = concat(as, bs) + } + + class Eff { + println("HI") + } + class Deco1(val as: HList) extends Eff { + transparent def ++ (bs: HList) = concat(as, bs) + } + + // Test that selections from impure classes cannot be projected away + + val rr = new Deco(HCons(1, HNil)) ++ HNil + val rra: HCons[Int, HNil.type] = rr // ok + val rr2 = new Deco2(HCons(1, HNil)) ++ HNil + val rr2a: HCons[Int, HNil.type] = rr2 // ok + val rr0 = new Deco0(HCons(1, HNil)) ++ HNil // error Maximal number of successive inlines (32) exceeded + val rr0a: HCons[Int, HNil.type] = rr0 // error (type error because no inline) + val rr1 = new Deco1(HCons(1, HNil)) ++ HNil + val rr1a: HCons[Int, HNil.type] = rr1 // error (type error because no inline) + + class Deco2(val as: HList) extends java.lang.Cloneable with java.lang.Comparable[Deco2] { + transparent def ++ (bs: HList) = concat(as, bs) + } +} \ No newline at end of file diff --git a/tests/neg/wildbase.scala b/tests/neg/wildbase.scala index b96607ae4e4a..b33abe6346b9 100644 --- a/tests/neg/wildbase.scala +++ b/tests/neg/wildbase.scala @@ -1,5 +1,5 @@ class A[T] -class B extends A[_] // OK +class B extends A[_] // error: type argument must be fully defined -class C extends A[_ >: Any <: Nothing] // error: conflicting bounds +class C extends A[_ >: Any <: Nothing] // error: conflicting bounds // error: type argument must be fully defined diff --git a/tests/pickling/i2166.scala b/tests/pickling/i2166.scala index f796031d009d..9b4addb2436b 100644 --- a/tests/pickling/i2166.scala +++ b/tests/pickling/i2166.scala @@ -1,5 +1,5 @@ object Test { - inline def f = "" match { case _ => false } + transparent def f = "" match { case _ => false } def main(args: Array[String]): Unit = f } \ No newline at end of file diff --git a/tests/pickling/i3608.scala b/tests/pickling/i3608.scala index 2751d5d017dd..f4131874cbbc 100644 --- a/tests/pickling/i3608.scala +++ b/tests/pickling/i3608.scala @@ -1,6 +1,6 @@ class A { class Foo { - inline def inlineMeth: Unit = new Bar + transparent def inlineMeth: Unit = new Bar } class Bar } diff --git a/tests/pickling/i4006.scala b/tests/pickling/i4006.scala index ef86dbb052f1..485fdbe59d55 100644 --- a/tests/pickling/i4006.scala +++ b/tests/pickling/i4006.scala @@ -1,4 +1,4 @@ class Foo { - inline def foo: Int = try { 1 } finally println("Hello") + transparent def foo: Int = try { 1 } finally println("Hello") foo } diff --git a/tests/pickling/i4006b.scala b/tests/pickling/i4006b.scala index 3c17a6522ac4..286d8a64435a 100644 --- a/tests/pickling/i4006b.scala +++ b/tests/pickling/i4006b.scala @@ -1,4 +1,4 @@ class Foo { - inline def foo: Int = try { 1 } catch { case _ => 4 } finally println("Hello") + transparent def foo: Int = try { 1 } catch { case _ => 4 } finally println("Hello") foo } diff --git a/tests/pickling/i4006c.scala b/tests/pickling/i4006c.scala index 9233ccdfc922..fd1951008f20 100644 --- a/tests/pickling/i4006c.scala +++ b/tests/pickling/i4006c.scala @@ -1,4 +1,4 @@ class Foo { - inline def foo: Int = try { 1 } catch { case _ => 4 } + transparent def foo: Int = try { 1 } catch { case _ => 4 } foo } diff --git a/tests/pickling/transparent.scala b/tests/pickling/transparent.scala new file mode 100644 index 000000000000..fbf01d91508e --- /dev/null +++ b/tests/pickling/transparent.scala @@ -0,0 +1,9 @@ +class Foo { + transparent def foo() = { + abstract class C[T] extends Object { + def x: T + println(x) + } + () + } +} diff --git a/tests/pos/SI-7060.scala b/tests/pos/SI-7060.scala index d6f172d45283..7e0eab453b61 100644 --- a/tests/pos/SI-7060.scala +++ b/tests/pos/SI-7060.scala @@ -1,6 +1,6 @@ object Test { - inline final def mbarray_apply_minibox(array: Any, tag: Byte): Long = + transparent final def mbarray_apply_minibox(array: Any, tag: Byte): Long = if (tag == 0) { array.asInstanceOf[Array[Long]](0) } else diff --git a/tests/pos/depfuntype.scala b/tests/pos/depfuntype.scala index 308b7aecc58d..43afbb0c434b 100644 --- a/tests/pos/depfuntype.scala +++ b/tests/pos/depfuntype.scala @@ -19,7 +19,7 @@ object Test { // Reproduced here because the one from DottyPredef is lacking a Tasty tree and // therefore can't be inlined when testing non-bootstrapped. // But inlining `implicitly` is vital to make the definition of `ifun` below work. - inline final def implicitly[T](implicit ev: T): T = ev + transparent final def implicitly[T](implicit ev: T): T = ev type IDF = implicit (x: C) => x.M diff --git a/tests/pos/harmonize.scala b/tests/pos/harmonize.scala index afbbeb058bae..ea0f60c867c2 100644 --- a/tests/pos/harmonize.scala +++ b/tests/pos/harmonize.scala @@ -3,7 +3,7 @@ object Test { def main(args: Array[String]) = { val x = true val n = 1 - inline val nn = 2 + transparent val nn = 2 val y = if (x) 'A' else n val z: Int = y @@ -41,7 +41,7 @@ object Test { } - inline val b = 33 + transparent val b = 33 def f(): Int = b + 1 val a1 = Array(b, 33, 'a') val b1: Array[Int] = a1 diff --git a/tests/pos/i1570.decompiled b/tests/pos/i1570.decompiled index 3f4d040e376b..31051997bd70 100644 --- a/tests/pos/i1570.decompiled +++ b/tests/pos/i1570.decompiled @@ -1,5 +1,5 @@ /** Decompiled from out/posTestFromTasty/pos/i1570/Test.class */ object Test { - inline def foo(n: scala.Int): scala.Int = Test.bar(n) - inline def bar(n: scala.Int): scala.Int = n + transparent def foo(n: scala.Int): scala.Int = Test.bar(n) + transparent def bar(n: scala.Int): scala.Int = n } diff --git a/tests/pos/i1570.scala b/tests/pos/i1570.scala index c2e4fd01b02d..c3fa7538d800 100644 --- a/tests/pos/i1570.scala +++ b/tests/pos/i1570.scala @@ -1,4 +1,4 @@ object Test { - inline def foo(inline n: Int) = bar(n) - inline def bar(inline n: Int) = n + transparent def foo(transparent n: Int) = bar(n) + transparent def bar(transparent n: Int) = n } diff --git a/tests/pos/i1891.scala b/tests/pos/i1891.scala index 794b27a35e8e..d994e6d4118e 100644 --- a/tests/pos/i1891.scala +++ b/tests/pos/i1891.scala @@ -4,7 +4,7 @@ object Test { type T2[A, B] = CC2[A, B] class ArrowAssoc[A](val self: A) { - inline def f[B](y: B): CC2[A, B] = new CC2(self, y) + transparent def f[B](y: B): CC2[A, B] = new CC2(self, y) } def foo = (new ArrowAssoc(1)).f(2) diff --git a/tests/pos/i1990.scala b/tests/pos/i1990.scala index 77cea0af73d3..432b17f253fe 100644 --- a/tests/pos/i1990.scala +++ b/tests/pos/i1990.scala @@ -1,6 +1,6 @@ class A { class Foo { - inline def inlineMeth: Unit = { + transparent def inlineMeth: Unit = { new Bar } } diff --git a/tests/pos/i1990a.scala b/tests/pos/i1990a.scala index f6f95ee364ed..d01cd8460019 100644 --- a/tests/pos/i1990a.scala +++ b/tests/pos/i1990a.scala @@ -1,6 +1,6 @@ class A { self => class Foo { - inline def inlineMeth: Unit = { + transparent def inlineMeth: Unit = { println(self) } } diff --git a/tests/pos/i2056.scala b/tests/pos/i2056.scala index c4d020fb6281..7a19810ec46b 100644 --- a/tests/pos/i2056.scala +++ b/tests/pos/i2056.scala @@ -1,5 +1,5 @@ object Test { - inline def crash() = { + transparent def crash() = { try { println("hi") } catch { diff --git a/tests/pos/i2104b.decompiled b/tests/pos/i2104b.decompiled index 37893eea43f5..9be9677bea4a 100644 --- a/tests/pos/i2104b.decompiled +++ b/tests/pos/i2104b.decompiled @@ -31,7 +31,7 @@ case class Pair[A, B](_1: A, _2: B) { throw new java.lang.IndexOutOfBoundsException(n.toString()) } } -object Pair +object Pair extends scala.AnyRef() /** Decompiled from out/posTestFromTasty/pos/i2104b/Test.class */ object Test { def main(args: scala.Array[scala.Predef.String]): scala.Unit = { @@ -40,4 +40,4 @@ object Test { () } } -} +} \ No newline at end of file diff --git a/tests/pos/i2980.scala b/tests/pos/i2980.scala index b28b5614c921..7512c35bfed1 100644 --- a/tests/pos/i2980.scala +++ b/tests/pos/i2980.scala @@ -3,7 +3,7 @@ trait Foo { } object Foo { - inline def foo: Foo = new Foo { + transparent def foo: Foo = new Foo { def apply[~>[_,_]](x: Int ~> Int): Int ~> Int = x } diff --git a/tests/pos/i3050.scala b/tests/pos/i3050.scala index 799dbdc1a0dc..6469bf3dad42 100644 --- a/tests/pos/i3050.scala +++ b/tests/pos/i3050.scala @@ -1,11 +1,39 @@ object Test { - inline def openImpl(): Int = + trait Option[+T] + case object None extends Option[Nothing] + case class Some[+T](x: T) extends Option[T] + + transparent def openImpl(): Int = Some(42) match { case Some(i) => i } - def open(): Int = openImpl() + def open() = openImpl() + + transparent def openImpl1(): Int = + new Some(42) match { case Some(i) => i } - inline def openImpl2(): Int = - Some(42) match { case None => 42 } + def open1() = openImpl1() + + transparent def openImpl2(): Int = + None match { case None => 42 } def open2(): Int = openImpl2() } + +// Same as Test, with Scala2 case classes +object Test2 { + transparent def openImpl(): Int = + Some(42) match { case Some(i) => i } + + def open() = openImpl() + + transparent def openImpl1(): Int = + new Some(42) match { case Some(i) => i } + + def open1() = openImpl1() + + transparent def openImpl2(): Int = + None match { case None => 42 } + + def open2(): Int = openImpl2() + +} diff --git a/tests/pos/i3082.scala b/tests/pos/i3082.scala index eece42fa92aa..3b4b285bc2fb 100644 --- a/tests/pos/i3082.scala +++ b/tests/pos/i3082.scala @@ -1,6 +1,6 @@ object Test { private def foo(arg1: Int): Int = { - inline def bar: Int = foo(0) + transparent def bar: Int = foo(0) if (arg1 == 0) 0 else bar } assert(foo(11) == 0) diff --git a/tests/pos/i3129.scala b/tests/pos/i3129.scala index a864a03a204c..85b248b52428 100644 --- a/tests/pos/i3129.scala +++ b/tests/pos/i3129.scala @@ -1,5 +1,5 @@ object companions2 { - inline def foo() = { + transparent def foo() = { class C { println(C.p) } @@ -15,7 +15,7 @@ class A { class B { private def getAncestor2(p: A): A = p - private inline def getAncestor(p: A): A = { + private transparent def getAncestor(p: A): A = { p.b.getAncestor(p) } } diff --git a/tests/pos/i3130a.scala b/tests/pos/i3130a.scala index b752986a044e..c3e2b0115e50 100644 --- a/tests/pos/i3130a.scala +++ b/tests/pos/i3130a.scala @@ -5,6 +5,6 @@ object O { class D(val x: Int) { class DD() object DD { - inline def apply() = x // new DD() + transparent def apply() = x // new DD() } } diff --git a/tests/pos/i3130b.scala b/tests/pos/i3130b.scala index 09f12ff0441c..ac2d63766eec 100644 --- a/tests/pos/i3130b.scala +++ b/tests/pos/i3130b.scala @@ -1,6 +1,6 @@ class Outer { trait F { def f(): Int } - inline def inner: F = { + transparent def inner: F = { class InnerClass(x: Int) extends F { def this() = this(3) def f() = x diff --git a/tests/pos/i3130c.scala b/tests/pos/i3130c.scala index 39b31a81e088..70610330588a 100644 --- a/tests/pos/i3130c.scala +++ b/tests/pos/i3130c.scala @@ -6,7 +6,7 @@ trait Test { trait TreeBuilder { val global: Global - inline def set(tree: global.Tree) = {} + transparent def set(tree: global.Tree) = {} } val nsc: Global diff --git a/tests/pos/i3130d.scala b/tests/pos/i3130d.scala index 1d375892cd8f..760955167dfb 100644 --- a/tests/pos/i3130d.scala +++ b/tests/pos/i3130d.scala @@ -1,6 +1,6 @@ class D(x: Int) { class DD { - inline def apply() = new DD() + transparent def apply() = new DD() } val inner = new DD } diff --git a/tests/pos/i3488.scala b/tests/pos/i3488.scala index 762ec4b7df30..2f4c1a3a3589 100644 --- a/tests/pos/i3488.scala +++ b/tests/pos/i3488.scala @@ -4,7 +4,7 @@ class Sett[A] { def incl(elem: A): Sett[A] = ??? - inline final def + (elem: A): Sett[A] = incl(elem) + transparent final def + (elem: A): Sett[A] = incl(elem) } object Sett { diff --git a/tests/pos/i3597.scala b/tests/pos/i3597.scala deleted file mode 100644 index 04e33975ea2c..000000000000 --- a/tests/pos/i3597.scala +++ /dev/null @@ -1,3 +0,0 @@ -object Test { - def bar(inline n: Int) = n -} diff --git a/tests/pos/i3608.scala b/tests/pos/i3608.scala index 2751d5d017dd..f4131874cbbc 100644 --- a/tests/pos/i3608.scala +++ b/tests/pos/i3608.scala @@ -1,6 +1,6 @@ class A { class Foo { - inline def inlineMeth: Unit = new Bar + transparent def inlineMeth: Unit = new Bar } class Bar } diff --git a/tests/pos/i3633.scala b/tests/pos/i3633.scala index a021e9ca077f..0eb758310026 100644 --- a/tests/pos/i3633.scala +++ b/tests/pos/i3633.scala @@ -1,4 +1,4 @@ class Test { - inline def foo = 1 + transparent def foo = 1 def test = -foo } diff --git a/tests/pos/i3636.scala b/tests/pos/i3636.scala index 9597bf25ba8b..768ff7a7ddec 100644 --- a/tests/pos/i3636.scala +++ b/tests/pos/i3636.scala @@ -1,11 +1,11 @@ trait Iterable[A] { def concat[B >: A](that: Iterable[B]): Iterable[B] = ??? - inline final def ++ [B >: A](that: Iterable[B]): Iterable[B] = concat(that) + transparent final def ++ [B >: A](that: Iterable[B]): Iterable[B] = concat(that) } class BitSet extends Iterable[Int] { def concat(that: Iterable[Int]): BitSet = ??? - inline final def ++ (that: Iterable[Int]): BitSet = concat(that) + transparent final def ++ (that: Iterable[Int]): BitSet = concat(that) } class Test { diff --git a/tests/pos/i3873.scala b/tests/pos/i3873.scala index ebb8b1cd70c9..ea9aa41c8ec6 100644 --- a/tests/pos/i3873.scala +++ b/tests/pos/i3873.scala @@ -1,6 +1,6 @@ object Test { - inline def sum2(ys: List[Int]): Unit = { + transparent def sum2(ys: List[Int]): Unit = { ys.foldLeft(1) } - val h1: ((List[Int]) => Unit) = sum2 + val h1 = (xs: List[Int]) => sum2(xs) } diff --git a/tests/pos/i3898/quoted_1.scala b/tests/pos/i3898/quoted_1.scala index 61fa38781138..f59c55ac94f5 100644 --- a/tests/pos/i3898/quoted_1.scala +++ b/tests/pos/i3898/quoted_1.scala @@ -1,5 +1,5 @@ import scala.quoted._ object Macro { - inline def ff(args: Any*): String = ~impl('(args)) + transparent def ff(args: Any*): String = ~impl('(args)) def impl(args: Expr[Seq[Any]]): Expr[String] = '("") } diff --git a/tests/pos/i3898b/quoted_1.scala b/tests/pos/i3898b/quoted_1.scala index fb2072ab4cb9..ca551862af67 100644 --- a/tests/pos/i3898b/quoted_1.scala +++ b/tests/pos/i3898b/quoted_1.scala @@ -1,5 +1,5 @@ import scala.quoted._ object Macro { - inline def ff(x: Int, inline y: Int): String = ~impl('(x)) + transparent def ff(x: Int, transparent y: Int): String = ~impl('(x)) def impl(x: Expr[Int]): Expr[String] = '("") } diff --git a/tests/pos/i3898c/quoted_1.scala b/tests/pos/i3898c/quoted_1.scala index fb2072ab4cb9..ca551862af67 100644 --- a/tests/pos/i3898c/quoted_1.scala +++ b/tests/pos/i3898c/quoted_1.scala @@ -1,5 +1,5 @@ import scala.quoted._ object Macro { - inline def ff(x: Int, inline y: Int): String = ~impl('(x)) + transparent def ff(x: Int, transparent y: Int): String = ~impl('(x)) def impl(x: Expr[Int]): Expr[String] = '("") } diff --git a/tests/pos/i3912-1/i3912_1.scala b/tests/pos/i3912-1/i3912_1.scala index 80faaf976743..63c9565dacd7 100644 --- a/tests/pos/i3912-1/i3912_1.scala +++ b/tests/pos/i3912-1/i3912_1.scala @@ -1,7 +1,7 @@ import scala.quoted._ object Macros { - inline def foo(): Int = { ~impl() } + transparent def foo(): Int = { ~impl() } def impl(): Expr[Int] = '(1) } \ No newline at end of file diff --git a/tests/pos/i3912-2/i3912_1.scala b/tests/pos/i3912-2/i3912_1.scala index e3d1ba91df37..828d161e2609 100644 --- a/tests/pos/i3912-2/i3912_1.scala +++ b/tests/pos/i3912-2/i3912_1.scala @@ -1,7 +1,7 @@ import scala.quoted._ object Macros { - inline def foo2(): Unit = ~impl() + transparent def foo2(): Unit = ~impl() def impl(): Expr[Int] = '(1) } \ No newline at end of file diff --git a/tests/pos/i3912-3/i3912_1.scala b/tests/pos/i3912-3/i3912_1.scala index a544cfd26282..3d0a78eb491e 100644 --- a/tests/pos/i3912-3/i3912_1.scala +++ b/tests/pos/i3912-3/i3912_1.scala @@ -1,7 +1,7 @@ import scala.quoted._ object Macros { - inline def foo3(): Int = { + transparent def foo3(): Int = { { ~impl() } diff --git a/tests/pos/i4023/Macro_1.scala b/tests/pos/i4023/Macro_1.scala index b51c1de87b2e..a68cf3467f05 100644 --- a/tests/pos/i4023/Macro_1.scala +++ b/tests/pos/i4023/Macro_1.scala @@ -1,5 +1,5 @@ import scala.quoted._ object Macro { - inline def ff[T: Type](x: T): T = ~impl('(x)) + transparent def ff[T: Type](x: T): T = ~impl('(x)) def impl[T](x: Expr[T]): Expr[T] = x } diff --git a/tests/pos/i4023b/Macro_1.scala b/tests/pos/i4023b/Macro_1.scala index e440b4e1923a..e1d1559c649a 100644 --- a/tests/pos/i4023b/Macro_1.scala +++ b/tests/pos/i4023b/Macro_1.scala @@ -1,5 +1,5 @@ import scala.quoted._ object Macro { - inline def ff[T](implicit t: Type[T]): Int = ~impl[T] + transparent def ff[T](implicit t: Type[T]): Int = ~impl[T] def impl[T]: Expr[Int] = '(4) } diff --git a/tests/pos/i4023c/Macro_1.scala b/tests/pos/i4023c/Macro_1.scala index 7e5714514b8d..1f5521b974c7 100644 --- a/tests/pos/i4023c/Macro_1.scala +++ b/tests/pos/i4023c/Macro_1.scala @@ -1,5 +1,5 @@ import scala.quoted._ object Macro { - inline def ff[T](x: T): T = ~impl('(x), '[T]) + transparent def ff[T](x: T): T = ~impl('(x), '[T]) def impl[T](x: Expr[T], t: Type[T]): Expr[T] = '{ (~x): ~t } } diff --git a/tests/pos/i4322.scala b/tests/pos/i4322.scala index 70039b7a5e17..dcddc7da1e84 100644 --- a/tests/pos/i4322.scala +++ b/tests/pos/i4322.scala @@ -3,12 +3,12 @@ object Foo { private def x(n: String): Int = n.toInt - inline def foo: Int = x + x + x("22") + transparent def foo: Int = x + x + x("22") - inline def bar = { + transparent def bar = { x += 1 x += 1 } - inline def baz = { x += x("11") } + transparent def baz = { x += x("11") } } diff --git a/tests/pos/i4493-b.scala b/tests/pos/i4493-b.scala index fdc3b6cf7867..25b043cb56ad 100644 --- a/tests/pos/i4493-b.scala +++ b/tests/pos/i4493-b.scala @@ -1,6 +1,6 @@ class Index[K] object Index { - inline def succ[K](x: K): Unit = ~{ + transparent def succ[K](x: K): Unit = ~{ implicit val t: quoted.Type[K] = '[K] '(new Index[K]) } diff --git a/tests/pos/i4493-c.scala b/tests/pos/i4493-c.scala index 08327e5fa81a..0b81937d3714 100644 --- a/tests/pos/i4493-c.scala +++ b/tests/pos/i4493-c.scala @@ -1,6 +1,6 @@ class Index[K] object Index { - inline def succ[K]: Unit = ~{ + transparent def succ[K]: Unit = ~{ '(new Index[K]) } } diff --git a/tests/pos/i4493.scala b/tests/pos/i4493.scala index 4d125d31ec6e..3ea8f4f23d5f 100644 --- a/tests/pos/i4493.scala +++ b/tests/pos/i4493.scala @@ -1,6 +1,6 @@ class Index[K] object Index { - inline def succ[K]: Unit = ~{ + transparent def succ[K]: Unit = ~{ implicit val t: quoted.Type[K] = '[K] '(new Index[K]) } diff --git a/tests/pos/i4514.scala b/tests/pos/i4514.scala index 0da1e9af33f7..dcb17865cb3a 100644 --- a/tests/pos/i4514.scala +++ b/tests/pos/i4514.scala @@ -1,4 +1,4 @@ object Foo { - inline def foo[X](x: X): Unit = ~fooImpl('(x)) + transparent def foo[X](x: X): Unit = ~fooImpl('(x)) def fooImpl[X: quoted.Type](x: X): quoted.Expr[Unit] = '() } diff --git a/tests/pos/i4586.scala b/tests/pos/i4586.scala index 7181a1844f49..12ba8a8f7c94 100644 --- a/tests/pos/i4586.scala +++ b/tests/pos/i4586.scala @@ -1,4 +1,4 @@ class Foo { - inline def foo1(f: => Int => Int): Int = f(7) + transparent def foo1(f: => Int => Int): Int = f(7) def bar1 = foo1(x => x + 1) } diff --git a/tests/pos/i4590.scala b/tests/pos/i4590.scala index 5ca26e197f2d..bf07b0e3a08c 100644 --- a/tests/pos/i4590.scala +++ b/tests/pos/i4590.scala @@ -1,7 +1,7 @@ package test class ArrayDeque { - inline def isResizeNecessary(len: Int) = len > ArrayDeque.StableSize + transparent def isResizeNecessary(len: Int) = len > ArrayDeque.StableSize } object ArrayDeque { @@ -9,7 +9,7 @@ object ArrayDeque { } class List { - inline def foo(x: List.Cons): Unit = { + transparent def foo(x: List.Cons): Unit = { x.next = this } } diff --git a/tests/pos/inline-access-levels/A_1.scala b/tests/pos/inline-access-levels/A_1.scala index 97f9392b17c1..6bbb3a44d173 100644 --- a/tests/pos/inline-access-levels/A_1.scala +++ b/tests/pos/inline-access-levels/A_1.scala @@ -4,7 +4,7 @@ object A { private var x: Int = 0 - inline def actOnX(f: Int => Int) = { + transparent def actOnX(f: Int => Int) = { x = f(x) } } diff --git a/tests/pos/inline-apply.scala b/tests/pos/inline-apply.scala index 6cdfe3b3ccb2..66a5c392512f 100644 --- a/tests/pos/inline-apply.scala +++ b/tests/pos/inline-apply.scala @@ -3,7 +3,7 @@ class Context object Test { def transform()(implicit ctx: Context) = { - inline def withLocalOwner[T](op: Context => T) = op(ctx) + transparent def withLocalOwner[T](op: Context => T) = op(ctx) withLocalOwner { implicit ctx => } diff --git a/tests/pos/inline-i2570.scala b/tests/pos/inline-i2570.scala index d68ae1dc8934..e520878c356c 100644 --- a/tests/pos/inline-i2570.scala +++ b/tests/pos/inline-i2570.scala @@ -1,4 +1,4 @@ object Test { - inline def sum2(ys: List[Int]): Int = (1 /: ys)(_ + _) - val h1: ((List[Int]) => Int) = sum2 + transparent def sum2(ys: List[Int]): Int = (1 /: ys)(_ + _) + val h1 = (xs: List[Int]) => sum2(xs) } diff --git a/tests/pos/inline-named-typeargs.scala b/tests/pos/inline-named-typeargs.scala index 9d2c5b3f4af9..3d09f1af8c22 100644 --- a/tests/pos/inline-named-typeargs.scala +++ b/tests/pos/inline-named-typeargs.scala @@ -1,5 +1,5 @@ object t1 { - inline def construct[Elem, Coll[_]](xs: List[Elem]): Coll[Elem] = ??? + transparent def construct[Elem, Coll[_]](xs: List[Elem]): Coll[Elem] = ??? val xs3 = construct[Coll = List](List(1, 2, 3)) } diff --git a/tests/pos/inline-t2425.scala b/tests/pos/inline-t2425.scala index f5a1602a4a46..09f4e218ed48 100644 --- a/tests/pos/inline-t2425.scala +++ b/tests/pos/inline-t2425.scala @@ -1,5 +1,5 @@ object Test extends App { - inline def foo[T](bar: T) = { + transparent def foo[T](bar: T) = { bar match { case _ => () } diff --git a/tests/pos/inline-t9232a.scala b/tests/pos/inline-t9232a.scala deleted file mode 100644 index 41f34adbe026..000000000000 --- a/tests/pos/inline-t9232a.scala +++ /dev/null @@ -1,11 +0,0 @@ -final class Foo(val value: Int) - -object Foo { - inline def unapply(foo: Foo): Some[Int] = Some(foo.value) -} - -object Test { - def transformTree(f: Foo): Any = f match { - case Foo(_) => ??? - } -} diff --git a/tests/pos/inline-t9232b.scala b/tests/pos/inline-t9232b.scala deleted file mode 100644 index 3cee6875097f..000000000000 --- a/tests/pos/inline-t9232b.scala +++ /dev/null @@ -1,23 +0,0 @@ -final class Foo(val value: Int) - -object Foo { - inline def unapplySeq(foo: Foo): Some[Seq[Int]] = Some(List(foo.value)) -} - -sealed trait Tree -case class Node1(foo: Foo) extends Tree -case class Node2() extends Tree - -object Test { - def transformTree(tree: Tree): Any = tree match { - case Node1(Foo(_: _*)) => ??? - } - - def transformTree2(tree: Tree): Any = tree match { - case Node1(Foo(1, _: _*)) => ??? - } - - def transformTree3(tree: Tree): Any = tree match { - case Node1(Foo(x, _: _*)) => ??? - } -} diff --git a/tests/pos/inlineAccesses.scala b/tests/pos/inlineAccesses.scala index 0305e0f37168..53fbd5aa49b8 100644 --- a/tests/pos/inlineAccesses.scala +++ b/tests/pos/inlineAccesses.scala @@ -6,12 +6,12 @@ class C { private type T = C private var x = 0 - inline def f = { + transparent def f = { x += 1 x = x + 1 x } - inline def dup = new T + transparent def dup = new T } object Test { diff --git a/tests/pos/inliner2.scala b/tests/pos/inliner2.scala index 7b9099310a32..4d2acdc64d3e 100644 --- a/tests/pos/inliner2.scala +++ b/tests/pos/inliner2.scala @@ -3,7 +3,7 @@ // for inlining due to the bug. class A { private var debug = false - inline private def ifelse[T](cond: => Boolean, ifPart: => T, elsePart: => T): T = + transparent private def ifelse[T](cond: => Boolean, ifPart: => T, elsePart: => T): T = if (cond) ifPart else elsePart final def bob1() = ifelse(debug, 1, 2) diff --git a/tests/pos/macro-with-array/Macro_1.scala b/tests/pos/macro-with-array/Macro_1.scala index a115aa3dc036..de42b4bb6726 100644 --- a/tests/pos/macro-with-array/Macro_1.scala +++ b/tests/pos/macro-with-array/Macro_1.scala @@ -1,29 +1,29 @@ object Macro { - inline def foo0(i: Int): Unit = ~{ '() } - inline def foo1(arr: Array[Boolean]): Unit = ~{ '() } - inline def foo2(arr: Array[Byte]): Unit = ~{ '() } - inline def foo3(arr: Array[Short]): Unit = ~{ '() } - inline def foo4(arr: Array[Int]): Unit = ~{ '() } - inline def foo5(arr: Array[Long]): Unit = ~{ '() } - inline def foo6(arr: Array[Float]): Unit = ~{ '() } - inline def foo7(arr: Array[Double]): Unit = ~{ '() } - inline def foo8(arr: Array[Char]): Unit = ~{ '() } - inline def foo9(arr: Array[Object]): Unit = ~{ '() } - inline def foo10(arr: Array[String]): Unit = ~{ '() } - inline def foo11[T](arr: Array[T]): Unit = ~{ '() } - inline def foo12(arr: Array[Array[Int]]): Unit = ~{ '() } - inline def foo13(arr: Array[Array[String]]): Unit = ~{ '() } - inline def foo14(arr: Array[Array[Array[Int]]]): Unit = ~{ '() } - inline def foo15(arr: Array[Any]): Unit = ~{ '() } - inline def foo16(arr: Array[AnyVal]): Unit = ~{ '() } - inline def foo17(arr: Array[AnyRef]): Unit = ~{ '() } - inline def foo18(arr: Array[Foo]): Unit = ~{ '() } - inline def foo19(arr: Array[Macro.type]): Unit = ~{ '() } - inline def foo20(arr: Array[Bar]): Unit = ~{ '() } - inline def foo21(arr: Array[Baz.type]): Unit = ~{ '() } - inline def foo22(arr: Array[Foo#A]): Unit = ~{ '() } + transparent def foo0(i: Int): Unit = ~{ '() } + transparent def foo1(arr: Array[Boolean]): Unit = ~{ '() } + transparent def foo2(arr: Array[Byte]): Unit = ~{ '() } + transparent def foo3(arr: Array[Short]): Unit = ~{ '() } + transparent def foo4(arr: Array[Int]): Unit = ~{ '() } + transparent def foo5(arr: Array[Long]): Unit = ~{ '() } + transparent def foo6(arr: Array[Float]): Unit = ~{ '() } + transparent def foo7(arr: Array[Double]): Unit = ~{ '() } + transparent def foo8(arr: Array[Char]): Unit = ~{ '() } + transparent def foo9(arr: Array[Object]): Unit = ~{ '() } + transparent def foo10(arr: Array[String]): Unit = ~{ '() } + transparent def foo11[T](arr: Array[T]): Unit = ~{ '() } + transparent def foo12(arr: Array[Array[Int]]): Unit = ~{ '() } + transparent def foo13(arr: Array[Array[String]]): Unit = ~{ '() } + transparent def foo14(arr: Array[Array[Array[Int]]]): Unit = ~{ '() } + transparent def foo15(arr: Array[Any]): Unit = ~{ '() } + transparent def foo16(arr: Array[AnyVal]): Unit = ~{ '() } + transparent def foo17(arr: Array[AnyRef]): Unit = ~{ '() } + transparent def foo18(arr: Array[Foo]): Unit = ~{ '() } + transparent def foo19(arr: Array[Macro.type]): Unit = ~{ '() } + transparent def foo20(arr: Array[Bar]): Unit = ~{ '() } + transparent def foo21(arr: Array[Baz.type]): Unit = ~{ '() } + transparent def foo22(arr: Array[Foo#A]): Unit = ~{ '() } class Bar object Baz diff --git a/tests/pos/macro-with-type/Macro_1.scala b/tests/pos/macro-with-type/Macro_1.scala index f6da5bf47794..f75c48270604 100644 --- a/tests/pos/macro-with-type/Macro_1.scala +++ b/tests/pos/macro-with-type/Macro_1.scala @@ -1,5 +1,5 @@ import scala.quoted._ object Macro { - inline def ff: Unit = ~impl('[Int]) + transparent def ff: Unit = ~impl('[Int]) def impl(t: Type[Int]): Expr[Unit] = '() } diff --git a/tests/pos/pos_valueclasses/t5853.scala b/tests/pos/pos_valueclasses/t5853.scala index b615cde71200..8edc734a4754 100644 --- a/tests/pos/pos_valueclasses/t5853.scala +++ b/tests/pos/pos_valueclasses/t5853.scala @@ -41,7 +41,7 @@ class Foo2 { object Arrow { implicit final class ArrowAssoc[A](val __leftOfArrow: A) extends AnyVal { - inline def ->>[B](y: B): Tuple2[A, B] = Tuple2(__leftOfArrow, y) + transparent def ->>[B](y: B): Tuple2[A, B] = Tuple2(__leftOfArrow, y) } def foo = 1 ->> 2 @@ -50,7 +50,7 @@ object Arrow { object SpecArrow { implicit final class ArrowAssoc[A](val __leftOfArrow: A) extends AnyVal { - inline def ->> [@specialized(Int) B](y: B): Tuple2[A, B] = Tuple2(__leftOfArrow, y) + transparent def ->> [@specialized(Int) B](y: B): Tuple2[A, B] = Tuple2(__leftOfArrow, y) } def foo = 1 ->> 2 diff --git a/tests/pos/power-macro/Macro_1.scala b/tests/pos/power-macro/Macro_1.scala index e279c7efd180..1881dcb467cc 100644 --- a/tests/pos/power-macro/Macro_1.scala +++ b/tests/pos/power-macro/Macro_1.scala @@ -3,7 +3,7 @@ import scala.quoted.Expr object PowerMacro { - inline def power(inline n: Long, x: Double) = ~powerCode(n, '(x)) + transparent def power(transparent n: Long, x: Double) = ~powerCode(n, '(x)) def powerCode(n: Long, x: Expr[Double]): Expr[Double] = if (n == 0) '(1.0) diff --git a/tests/pos/quote-0.scala b/tests/pos/quote-0.scala index ad413acba29a..cd90e9c24483 100644 --- a/tests/pos/quote-0.scala +++ b/tests/pos/quote-0.scala @@ -4,7 +4,7 @@ import dotty.tools.dotc.quoted.Toolbox._ object Macros { - inline def assert(expr: => Boolean): Unit = + transparent def assert(expr: => Boolean): Unit = ~assertImpl('(expr)) def assertImpl(expr: Expr[Boolean]) = @@ -12,7 +12,7 @@ object Macros { def showExpr[T](expr: Expr[T]): Expr[String] = expr.toString.toExpr - inline def power(inline n: Int, x: Double) = ~powerCode(n, '(x)) + transparent def power(transparent n: Int, x: Double) = ~powerCode(n, '(x)) def powerCode(n: Int, x: Expr[Double]): Expr[Double] = if (n == 0) '(1.0) diff --git a/tests/pos/quote-assert/quoted_2.scala b/tests/pos/quote-assert/quoted_2.scala index 337892490574..6c14e51241f7 100644 --- a/tests/pos/quote-assert/quoted_2.scala +++ b/tests/pos/quote-assert/quoted_2.scala @@ -4,7 +4,7 @@ import Macros._ object Test { - inline def assert(expr: => Boolean): Unit = + transparent def assert(expr: => Boolean): Unit = ~ assertImpl('(expr)) diff --git a/tests/pos/quote-lift-inline-params-b.scala b/tests/pos/quote-lift-inline-params-b.scala index a5e4d797c083..66872399d155 100644 --- a/tests/pos/quote-lift-inline-params-b.scala +++ b/tests/pos/quote-lift-inline-params-b.scala @@ -1,6 +1,6 @@ import scala.quoted.Expr object Macro { - inline def foo(inline n: Int): Int = ~{ + transparent def foo(transparent n: Int): Int = ~{ import quoted.Liftable.{IntIsLiftable => _} '(n) } diff --git a/tests/pos/quote-lift-inline-params/Macro_1.scala b/tests/pos/quote-lift-inline-params/Macro_1.scala index a5e4d797c083..66872399d155 100644 --- a/tests/pos/quote-lift-inline-params/Macro_1.scala +++ b/tests/pos/quote-lift-inline-params/Macro_1.scala @@ -1,6 +1,6 @@ import scala.quoted.Expr object Macro { - inline def foo(inline n: Int): Int = ~{ + transparent def foo(transparent n: Int): Int = ~{ import quoted.Liftable.{IntIsLiftable => _} '(n) } diff --git a/tests/pos/quote-nested-object/Macro_1.scala b/tests/pos/quote-nested-object/Macro_1.scala index a24a6dd3f376..cda302351c2b 100644 --- a/tests/pos/quote-nested-object/Macro_1.scala +++ b/tests/pos/quote-nested-object/Macro_1.scala @@ -6,7 +6,7 @@ object Macro { object Implementation { - inline def plus(inline n: Int, m: Int): Int = ~plus(n, '(m)) + transparent def plus(transparent n: Int, m: Int): Int = ~plus(n, '(m)) def plus(n: Int, m: Expr[Int]): Expr[Int] = if (n == 0) m @@ -14,7 +14,7 @@ object Macro { object Implementation2 { - inline def plus(inline n: Int, m: Int): Int = ~plus(n, '(m)) + transparent def plus(transparent n: Int, m: Int): Int = ~plus(n, '(m)) def plus(n: Int, m: Expr[Int]): Expr[Int] = if (n == 0) m diff --git a/tests/pos/rbtree.scala b/tests/pos/rbtree.scala index ffa44f743137..baba24a72eb7 100644 --- a/tests/pos/rbtree.scala +++ b/tests/pos/rbtree.scala @@ -457,11 +457,11 @@ object RedBlackTree { } object RedTree { - inline def apply[A, B](key: A, value: B, left: Tree[A, B], right: Tree[A, B]) = new RedTree(key, value, left, right) + transparent def apply[A, B](key: A, value: B, left: Tree[A, B], right: Tree[A, B]) = new RedTree(key, value, left, right) def unapply[A, B](t: RedTree[A, B]) = Some((t.key, t.value, t.left, t.right)) } object BlackTree { - inline def apply[A, B](key: A, value: B, left: Tree[A, B], right: Tree[A, B]) = new BlackTree(key, value, left, right) + transparent def apply[A, B](key: A, value: B, left: Tree[A, B], right: Tree[A, B]) = new BlackTree(key, value, left, right) def unapply[A, B](t: BlackTree[A, B]) = Some((t.key, t.value, t.left, t.right)) } diff --git a/tests/pos/reference/inlines.scala b/tests/pos/reference/inlines.scala index c83ca72d3cbf..f87510d0c927 100644 --- a/tests/pos/reference/inlines.scala +++ b/tests/pos/reference/inlines.scala @@ -1,14 +1,14 @@ package inlines object Config { - inline val logging = false + transparent val logging = false } object Logger { private var indent = 0 - inline def log[T](msg: String)(op: => T): T = + transparent def log[T](msg: String)(op: => T): T = if (Config.logging) { println(s"${" " * indent}start $msg") indent += 1 @@ -22,7 +22,6 @@ object Logger { object Test { import Logger._ - def factorial(n: BigInt): BigInt = log(s"factorial($n)") { if (n == 0) 1 diff --git a/tests/pos/sealed-final.scala b/tests/pos/sealed-final.scala index e062888e2668..fc2c551c1518 100644 --- a/tests/pos/sealed-final.scala +++ b/tests/pos/sealed-final.scala @@ -1,5 +1,5 @@ sealed abstract class Foo { - inline def bar(x: Int) = x + 1 + transparent def bar(x: Int) = x + 1 } object Foo { def mkFoo(): Foo = new Baz2 diff --git a/tests/pos/simpleCaseClass-3.decompiled b/tests/pos/simpleCaseClass-3.decompiled index d2a3acf557ea..6dcd9d72ab22 100644 --- a/tests/pos/simpleCaseClass-3.decompiled +++ b/tests/pos/simpleCaseClass-3.decompiled @@ -22,4 +22,4 @@ case class A[T](x: T) { throw new java.lang.IndexOutOfBoundsException(n.toString()) } } -object A +object A extends scala.AnyRef() \ No newline at end of file diff --git a/tests/pos/simpleInline.decompiled b/tests/pos/simpleInline.decompiled index 492a0da6e777..d2a4ff00f1eb 100644 --- a/tests/pos/simpleInline.decompiled +++ b/tests/pos/simpleInline.decompiled @@ -1,6 +1,6 @@ /** Decompiled from out/posTestFromTasty/pos/simpleInline/Foo.class */ class Foo() { - inline def foo: scala.Int = 9 + transparent def foo: scala.Int = 9 def bar: scala.Int = { // inlined 9 } diff --git a/tests/pos/simpleInline.scala b/tests/pos/simpleInline.scala index 4cfab0209754..6710461f8a0d 100644 --- a/tests/pos/simpleInline.scala +++ b/tests/pos/simpleInline.scala @@ -1,4 +1,4 @@ class Foo { - inline def foo: Int = 9 + transparent def foo: Int = 9 def bar: Int = foo } diff --git a/tests/pos/t6157.scala b/tests/pos/t6157.scala index 89e503c78990..5a0a7f52fea5 100644 --- a/tests/pos/t6157.scala +++ b/tests/pos/t6157.scala @@ -12,7 +12,7 @@ import java.io.IOException object ErrorHandler { - inline def defaultIfIOException[T](default: => T)(closure: => T): T = { + transparent def defaultIfIOException[T](default: => T)(closure: => T): T = { try { closure } catch { diff --git a/tests/pos/t6562.scala b/tests/pos/t6562.scala index bf8ed8679c1e..2750c6f4b203 100644 --- a/tests/pos/t6562.scala +++ b/tests/pos/t6562.scala @@ -1,11 +1,11 @@ class Test { - inline def foo: Unit = { + transparent def foo: Unit = { def it = new {} (_: Any) => it } - inline private def bar: Unit = { + transparent private def bar: Unit = { def it = new {} (_: Any) => it } diff --git a/tests/pos/tasty/definitions.scala b/tests/pos/tasty/definitions.scala index d3d8a2c38e1e..95341ac8cfcf 100644 --- a/tests/pos/tasty/definitions.scala +++ b/tests/pos/tasty/definitions.scala @@ -236,7 +236,7 @@ object definitions { def isLazy: Boolean def isOverride: Boolean def isInline: Boolean - def isMacro: Boolean // inline method containing toplevel splices + def isMacro: Boolean // transparent method containing toplevel splices def isStatic: Boolean // mapped to static Java member def isObject: Boolean // an object or its class (used for a ValDef or a ClassDef extends Modifier respectively) def isTrait: Boolean // a trait (used for a ClassDef) diff --git a/tests/pos/transparent-nested.scala b/tests/pos/transparent-nested.scala new file mode 100644 index 000000000000..7c7bd10941f4 --- /dev/null +++ b/tests/pos/transparent-nested.scala @@ -0,0 +1,39 @@ +object Test0 { + + def f(x: Int) = { + transparent def g(x: Int) = x match { + case 0 => 0 + } + g(0) + transparent val Y = 0 + g(Y) + + transparent def h(x: Int) = x match { + case Y => 0 + } + h(0) + } + + f(0) + +} + +object Test1 { + + erased transparent def f(x: Int) = { + erased transparent def g(x: Int) = x match { + case 0 => 0 + } + g(0) + transparent val Y = 0 + g(Y) + + transparent def h(x: Int) = x match { + case Y => 0 + } + h(0) + } + + f(0) + +} \ No newline at end of file diff --git a/tests/pos/transparent-overload.scala b/tests/pos/transparent-overload.scala new file mode 100644 index 000000000000..66189d16e6d9 --- /dev/null +++ b/tests/pos/transparent-overload.scala @@ -0,0 +1,22 @@ +object Test { + + def f(x: Int): Int = x + def f(x: String): String = x + def f(x: Any): Any = x + + transparent def g(x: Any) = f(x) + transparent def h(x: Any) = this.f(x) + + locally { + val x1 = g(1) + val x2 = g("bb") + val y1: Int = x1 + val y2: String = x2 + } + locally { + val x1 = h(1) + val x2 = h("bb") + val y1: Int = x1 + val y2: String = x2 + } +} \ No newline at end of file diff --git a/tests/pos/typelevel-vector1.scala b/tests/pos/typelevel-vector1.scala new file mode 100644 index 000000000000..d48dadd12331 --- /dev/null +++ b/tests/pos/typelevel-vector1.scala @@ -0,0 +1,30 @@ +object typelevel { + case class Typed[T](value: T) { type Type = T } + erased def erasedValue[T]: T = ??? +} + +trait Nat +case object Z extends Nat +case class S[N <: Nat](n: N) extends Nat + +case class Vec[T, N <: Nat](elems: List[T]) + +object Test { + import typelevel._ + type Z = Z.type + + transparent def add(x: Nat, y: Nat): Nat = x match { + case Z => y + case S(x1) => S(add(x1, y)) + } + + val x = S(S(Z)) + val y = add(x, x) + val z: S[S[S[S[Z]]]] = y + + transparent def concat[T, N1 <: Nat, N2 <: Nat](xs: Vec[T, N1], ys: Vec[T, N2]): Vec[T, _] = { + val length = Typed(add(erasedValue[N1], erasedValue[N2])) + Vec[T, length.Type](xs.elems ++ ys.elems) + } +} + diff --git a/tests/run-with-compiler/i3876-d.check b/tests/run-with-compiler/i3876-d.check index 53f5ee0793e4..746b7f8778a7 100644 --- a/tests/run-with-compiler/i3876-d.check +++ b/tests/run-with-compiler/i3876-d.check @@ -1,8 +1,5 @@ 6 { val x$1: scala.Int = 3 - - { // inlined - x$1.+(x$1) - } + x$1.+(x$1) } diff --git a/tests/run-with-compiler/i3876-d.scala b/tests/run-with-compiler/i3876-d.scala index 3f11547d4522..153ac52e9d2b 100644 --- a/tests/run-with-compiler/i3876-d.scala +++ b/tests/run-with-compiler/i3876-d.scala @@ -13,5 +13,5 @@ object Test { println(f4(x).show) } - inline def inlineLambda: Int => Int = x => x + x + transparent def inlineLambda: Int => Int = x => x + x } \ No newline at end of file diff --git a/tests/run/dead-code-elimination.scala b/tests/run/dead-code-elimination.scala index f5331f47973d..61d7eecdf717 100644 --- a/tests/run/dead-code-elimination.scala +++ b/tests/run/dead-code-elimination.scala @@ -18,7 +18,7 @@ final class A { def f1 = true def f2 = true - inline def f3 = f1 || f2 + transparent def f3 = f1 || f2 class B { def f() = 1 to 10 foreach (_ => f3) } diff --git a/tests/run/generic-tuples.scala b/tests/run/generic-tuples.scala new file mode 100644 index 000000000000..8a90a410db3d --- /dev/null +++ b/tests/run/generic-tuples.scala @@ -0,0 +1,17 @@ +package tuples { + +trait Tuple + +/** () in stdlib */ +class HNil extends Tuple +case object HNil extends HNil + +trait Pair[H, T <: Tuple] { + erased transparent def size = ??? +} +} + + +object Test extends App { + +} \ No newline at end of file diff --git a/tests/run/genericValueClass.scala b/tests/run/genericValueClass.scala index 689e0e251e2f..e2fdb79cc69b 100644 --- a/tests/run/genericValueClass.scala +++ b/tests/run/genericValueClass.scala @@ -3,12 +3,12 @@ import scala.language.implicitConversions object Test extends dotty.runtime.LegacyApp { class ArrowAssocClass[A](val __leftOfArrow: A) extends AnyVal { - inline def -> [B](y: B): Tuple2[A, B] = Tuple2(__leftOfArrow, y) + transparent def -> [B](y: B): Tuple2[A, B] = Tuple2(__leftOfArrow, y) def →[B](y: B): Tuple2[A, B] = ->(y) } { - inline implicit def ArrowAssoc[A](x: A): ArrowAssocClass[A] = new ArrowAssocClass(x) + transparent implicit def ArrowAssoc[A](x: A): ArrowAssocClass[A] = new ArrowAssocClass(x) val x = 1 -> "abc" println(x) } diff --git a/tests/run/i1569.scala b/tests/run/i1569.scala index 2c5dd4e5a0eb..ef64021e016e 100644 --- a/tests/run/i1569.scala +++ b/tests/run/i1569.scala @@ -1,5 +1,5 @@ object Test { - inline def foo(inline n: => Int) = n + n + transparent def foo(transparent n: => Int) = n + n def main(args: Array[String]): Unit = foo({ println("foo"); 42 }) } diff --git a/tests/run/i1990b.scala b/tests/run/i1990b.scala index 2460208dbf9f..63d6a5fd93c0 100644 --- a/tests/run/i1990b.scala +++ b/tests/run/i1990b.scala @@ -1,6 +1,6 @@ trait A { self => class Foo { - inline def inlineMeth: Unit = { + transparent def inlineMeth: Unit = { println(self) } } diff --git a/tests/run/i2077.scala b/tests/run/i2077.scala index 42b629b90a07..639dc4921472 100644 --- a/tests/run/i2077.scala +++ b/tests/run/i2077.scala @@ -1,5 +1,5 @@ object Test { - inline val x = true + transparent val x = true val y = if (x) 1 else 2 // reduced to val y = 1 def main(args: Array[String]): Unit = diff --git a/tests/run/i2360.scala b/tests/run/i2360.scala index d950d1c2d3dd..584cab6f4ce6 100644 --- a/tests/run/i2360.scala +++ b/tests/run/i2360.scala @@ -7,7 +7,7 @@ object Test { } object Foo extends Bar { - inline def foo: Int = bar + transparent def foo: Int = bar } class Bar { diff --git a/tests/run/i2895.scala b/tests/run/i2895.scala deleted file mode 100644 index 2bc2037ebaa7..000000000000 --- a/tests/run/i2895.scala +++ /dev/null @@ -1,17 +0,0 @@ -object Foo extends (Int => Int) { - inline def apply(x: Int): Int = impl(x) - def impl(x: Int): Int = x + 1 -} - -object Test { - - def test(foo: Foo.type): Int = foo(41) - - def test2(f: Int => Int): Int = f(41) - - def main(args: Array[String]): Unit = { - assert(test(Foo) == 42) - assert(test2(Foo) == 42) - } - -} diff --git a/tests/run/i2895a.scala b/tests/run/i2895a.scala index 72f6dd3d61db..a37ae2ce0943 100644 --- a/tests/run/i2895a.scala +++ b/tests/run/i2895a.scala @@ -3,7 +3,7 @@ trait Foo[A <: Foo[A]] { def next: A - inline final def once: A = next + transparent final def once: A = next def once1: A = once diff --git a/tests/run/i4431/quoted_1.scala b/tests/run/i4431/quoted_1.scala index 8f0be7b23ef7..ba4a4e743077 100644 --- a/tests/run/i4431/quoted_1.scala +++ b/tests/run/i4431/quoted_1.scala @@ -1,5 +1,5 @@ import scala.quoted._ object Macros { - inline def h(f: => Int => String): String = ~ '(f(42)) + transparent def h(f: => Int => String): String = ~ '(f(42)) } diff --git a/tests/run/i4455/Macro_1.scala b/tests/run/i4455/Macro_1.scala index 9ac3070ed986..840f3677a5b3 100644 --- a/tests/run/i4455/Macro_1.scala +++ b/tests/run/i4455/Macro_1.scala @@ -1,8 +1,8 @@ import scala.quoted._ object Macros { - inline def foo(inline i: Int): Int = ~bar('(i)) + transparent def foo(transparent i: Int): Int = ~bar('(i)) - inline def foo2(inline i: Int): Int = ~bar('(i + 1)) + transparent def foo2(transparent i: Int): Int = ~bar('(i + 1)) def bar(x: Expr[Int]): Expr[Int] = x } diff --git a/tests/run/i4492/quoted_1.scala b/tests/run/i4492/quoted_1.scala index 67e68d59c1e7..d41d07a29f4a 100644 --- a/tests/run/i4492/quoted_1.scala +++ b/tests/run/i4492/quoted_1.scala @@ -2,5 +2,5 @@ trait Index object Index { - inline def succ(prev: Index): Unit = ~{ '(println("Ok")) } + transparent def succ(prev: Index): Unit = ~{ '(println("Ok")) } } diff --git a/tests/run/i4496b.scala b/tests/run/i4496b.scala index 2e777f64e8ac..82d8967e5beb 100644 --- a/tests/run/i4496b.scala +++ b/tests/run/i4496b.scala @@ -24,7 +24,7 @@ object Test { // These accesses are also clearly well-typed def consume(v: T) = v.a - inline def consumeInl(v: T) = v.a + transparent def consumeInl(v: T) = v.a def verify(v: T) = { assert(consume(v) == 10) assert(consumeInl(v) == 10) @@ -58,7 +58,7 @@ object Test { def upcast2(v: Foo2): T = v def upcast3(v: Foo3): T = v def consume(v: T) = v.a - inline def consumeInl(v: T) = v.a + transparent def consumeInl(v: T) = v.a def verify(v: T) = { assert(consume(v) == 10) assert(consumeInl(v) == 10) @@ -88,7 +88,7 @@ object Test { type T = {val a: Int; def a_=(x: Int): Unit} def upcast3(v: Foo3): T = v def consume(v: T) = v.a - inline def consumeInl(v: T) = v.a + transparent def consumeInl(v: T) = v.a def verify(v: T) = { assert(consume(v) == 10) assert(consumeInl(v) == 10) diff --git a/tests/run/i4754.scala b/tests/run/i4754.scala index 0907880e6499..f2763526fa9a 100644 --- a/tests/run/i4754.scala +++ b/tests/run/i4754.scala @@ -6,7 +6,7 @@ object Foo { class Foo { import Foo._ - inline def foo = x + Foo.x + y + Foo.y + z + Foo.z + transparent def foo = x + Foo.x + y + Foo.y + z + Foo.z } object Test { diff --git a/tests/run/implicitMatch.check b/tests/run/implicitMatch.check new file mode 100644 index 000000000000..223b7836fb19 --- /dev/null +++ b/tests/run/implicitMatch.check @@ -0,0 +1 @@ +B diff --git a/tests/run/implicitMatch.scala b/tests/run/implicitMatch.scala new file mode 100644 index 000000000000..c11888e8897b --- /dev/null +++ b/tests/run/implicitMatch.scala @@ -0,0 +1,26 @@ +object Test extends App { + import collection.immutable.TreeSet + import collection.immutable.HashSet + + transparent def f[T]() = implicit match { + case ord: Ordering[T] => new TreeSet[T] + case _ => new HashSet[T] + } + + class A + class B + implicit val b: B = new B + + transparent def g = implicit match { + case _: A => println("A") + case _: B => println("B") + } + + implicitly[Ordering[String]] + + f[String]() + f[AnyRef]() + implicitly[B] + g + +} \ No newline at end of file diff --git a/tests/run/inlineAccess/C_1.scala b/tests/run/inlineAccess/C_1.scala deleted file mode 100644 index 349f5b1508dd..000000000000 --- a/tests/run/inlineAccess/C_1.scala +++ /dev/null @@ -1,7 +0,0 @@ -package p { -class C { - protected def f(): Unit = () - - inline def inl() = f() // error (when inlined): not accessible -} -} diff --git a/tests/run/inlinedAssign.scala b/tests/run/inlinedAssign.scala deleted file mode 100644 index 37e66833a0dc..000000000000 --- a/tests/run/inlinedAssign.scala +++ /dev/null @@ -1,24 +0,0 @@ -object Test { - - inline def swap[T](x: T, x_= : => T => Unit, y: T, y_= : => T => Unit) = { - x_=(y) - y_=(x) - } - - inline def f(x: Int => Unit) = x - - def main(args: Array[String]) = { - var x = 1 - var y = 2 - inline def setX(z: Int) = x = z - inline def setY(z: Int) = y = z - swap(x, setX, y, setY) - assert(x == 2 && y == 1) - - swap(x, x = _, y, y = _) - assert(x == 1 && y == 2) - - - val z = f(setX) // tests case where inline arg is not applied - } -} diff --git a/tests/run/lst/LstTest.check b/tests/run/lst.check similarity index 100% rename from tests/run/lst/LstTest.check rename to tests/run/lst.check diff --git a/tests/run/lst/Lst.scala b/tests/run/lst/Lst.scala index 3cdd4a076119..3f6500d475dc 100644 --- a/tests/run/lst/Lst.scala +++ b/tests/run/lst/Lst.scala @@ -18,6 +18,8 @@ import reflect.ClassTag class Lst[+T](val elems: Any) extends AnyVal { self => import Lst._ + transparent def locally[T](body: => T): T = body + def length: Int = elems match { case null => 0 case elems: Arr => elems.length @@ -27,7 +29,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => def isEmpty = elems == null def nonEmpty = elems != null - inline def foreach(op: => T => Unit): Unit = { + transparent def foreach(op: => T => Unit): Unit = locally { def sharedOp(x: T) = op(x) elems match { case null => @@ -38,7 +40,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => } } - inline def foreachReversed(inline op: T => Unit): Unit = { + transparent def foreachReversed(transparent op: T => Unit): Unit = locally { def sharedOp(x: T) = op(x) elems match { case null => @@ -52,12 +54,14 @@ class Lst[+T](val elems: Any) extends AnyVal { self => /** Like `foreach`, but completely inlines `op`, at the price of generating the code twice. * Should be used only of `op` is small */ - inline def foreachInlined(op: => T => Unit): Unit = elems match { - case null => - case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] - var i = 0 - while (i < elems.length) { op(elem(i)); i += 1 } - case elem: T @unchecked => op(elem) + transparent def foreachInlined(op: => T => Unit): Unit = locally { + elems match { + case null => + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < elems.length) { op(elem(i)); i += 1 } + case elem: T @unchecked => op(elem) + } } def iterator(): Iterator[T] = elems match { @@ -101,7 +105,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => } /** `f` is pulled out, not duplicated */ - inline def map[U](f: => T => U): Lst[U] = { + transparent def map[U](f: => T => U): Lst[U] = locally { def op(x: T) = f(x) elems match { case null => Empty @@ -193,7 +197,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => buf.toLst } - inline def exists(p: => T => Boolean): Boolean = { + transparent def exists(p: => T => Boolean): Boolean = locally { def op(x: T) = p(x) elems match { case null => false @@ -206,7 +210,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => } } - inline def forall(p: => T => Boolean): Boolean = { + transparent def forall(p: => T => Boolean): Boolean = locally { def op(x: T) = p(x) elems match { case null => true @@ -219,7 +223,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => } } - inline def contains[U >: T](x: U): Boolean = elems match { + def contains[U >: T](x: U): Boolean = elems match { case null => false case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] var i = 0 @@ -229,7 +233,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => elem == x } - inline def foldLeft[U](z: U)(f: => (U, T) => U) = { + transparent def foldLeft[U](z: U)(f: => (U, T) => U) = locally { def op(x: U, y: T) = f(x, y) elems match { case null => z @@ -243,7 +247,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => } } - inline def /: [U](z: U)(op: => (U, T) => U) = foldLeft(z)(op) + transparent def /: [U](z: U)(op: => (U, T) => U) = foldLeft(z)(op) def reduceLeft[U >: T](op: (U, U) => U) = elems match { case null => diff --git a/tests/run/lst/LstTest.scala b/tests/run/lst/LstTest.scala index 9e3caad751a0..9f7c7a7ff1c6 100644 --- a/tests/run/lst/LstTest.scala +++ b/tests/run/lst/LstTest.scala @@ -238,7 +238,7 @@ object Test extends App { assert(is10.contains(5)) assert(!is10.contains(6)) - assert(!xss0.contains(List(""))) + assert(!xss0.contains(Lst(""))) assert(xss1.contains(Lst("a"))) assert(xss10.contains(Lst("e")), xss10) assert(!xss10.contains(Lst("f"))) diff --git a/tests/run/nats.scala-deptypes b/tests/run/nats.scala-deptypes index 80b68336d7e3..a20e521bb5e2 100644 --- a/tests/run/nats.scala-deptypes +++ b/tests/run/nats.scala-deptypes @@ -11,11 +11,11 @@ object Nat { val Z = new Z } -type NatOf(inline x: Int where x >= 0) = +type NatOf(transparent x: Int where x >= 0) = if (x == 0) Z else S[NatOf(x - 1)] -inline def natOf(inline x: Int where x >= 0): NatOf(x) = +inline def natOf(transparent x: Int where x >= 0): NatOf(x) = if (x == 0) Z else S(natOf(x - 1)) diff --git a/tests/run/quote-and-splice/Macros_1.scala b/tests/run/quote-and-splice/Macros_1.scala index d8aa24abeeaa..08259d650966 100644 --- a/tests/run/quote-and-splice/Macros_1.scala +++ b/tests/run/quote-and-splice/Macros_1.scala @@ -2,22 +2,22 @@ import scala.quoted._ object Macros { - inline def macro1 = ~ macro1Impl + transparent def macro1 = ~ macro1Impl def macro1Impl = '(3) - inline def macro2(inline p: Boolean) = ~ macro2Impl(p) + transparent def macro2(transparent p: Boolean) = ~ macro2Impl(p) def macro2Impl(p: Boolean) = if (p) '(3) else '(4) - inline def macro3(n: Int) = ~ macro3Impl('(n)) + transparent def macro3(n: Int) = ~ macro3Impl('(n)) def macro3Impl(p: Expr[Int]) = '{ 2 + ~p } - inline def macro4(i: Int)(j: Int) = ~ macro4Impl('(i))('(j)) + transparent def macro4(i: Int)(j: Int) = ~ macro4Impl('(i))('(j)) def macro4Impl(i: Expr[Int])(j: Expr[Int]) = '{ ~i + ~j } - inline def macro5(i: Int, j: Int) = ~ macro5Impl(j = '(j), i = '(i)) + transparent def macro5(i: Int, j: Int) = ~ macro5Impl(j = '(j), i = '(i)) def macro5Impl(i: Expr[Int], j: Expr[Int]) = '{ ~i + ~j } - inline def power(inline n: Int, x: Double) = ~powerCode(n, '(x)) + transparent def power(transparent n: Int, x: Double) = ~powerCode(n, '(x)) def powerCode(n: Int, x: Expr[Double]): Expr[Double] = if (n == 0) '(1.0) diff --git a/tests/run/quote-force/quoted_1.scala b/tests/run/quote-force/quoted_1.scala index 47c01a3c3abf..c4eff1425993 100644 --- a/tests/run/quote-force/quoted_1.scala +++ b/tests/run/quote-force/quoted_1.scala @@ -4,7 +4,7 @@ case class Location(owners: List[String]) object Location { - implicit inline def location: Location = ~impl + implicit transparent def location: Location = ~impl def impl: Expr[Location] = { val list = List("a", "b", "c", "d", "e", "f") diff --git a/tests/run/quote-impure-by-name/quoted_1.scala b/tests/run/quote-impure-by-name/quoted_1.scala index 5b83d7c37d93..6e569fab4765 100644 --- a/tests/run/quote-impure-by-name/quoted_1.scala +++ b/tests/run/quote-impure-by-name/quoted_1.scala @@ -9,7 +9,7 @@ object Index { implicit def zero[K, T]: Index[K, (K, T)] = new Index("0") - implicit inline def succ[K, H, T](implicit prev: => Index[K, T]): Index[K, (H, T)] = ~succImpl('(prev))('[K], '[H], '[T]) + implicit transparent def succ[K, H, T](implicit prev: => Index[K, T]): Index[K, (H, T)] = ~succImpl('(prev))('[K], '[H], '[T]) def succImpl[K, H, T](prev: Expr[Index[K, T]])(implicit k: Type[K], h: Type[H], t: Type[T]): Expr[Index[K, (H, T)]] = { val value = s"1 + {${prev.show}}" diff --git a/tests/run/quote-indexed-map-by-name/quoted_1.scala b/tests/run/quote-indexed-map-by-name/quoted_1.scala index 32ed34bac0e6..2a7003bc90e9 100644 --- a/tests/run/quote-indexed-map-by-name/quoted_1.scala +++ b/tests/run/quote-indexed-map-by-name/quoted_1.scala @@ -5,7 +5,7 @@ object Index { implicit def zero[K, T]: Index[K, (K, T)] = new Index(0) - implicit inline def succ[K, H, T](implicit prev: => Index[K, T]): Index[K, (H, T)] = ~succImpl('[K], '[H], '[T]) + implicit transparent def succ[K, H, T](implicit prev: => Index[K, T]): Index[K, (H, T)] = ~succImpl('[K], '[H], '[T]) def succImpl[K, H, T](implicit k: Type[K], h: Type[H], t: Type[T]): Expr[Index[K, (H, T)]] = { '(new Index(0)) diff --git a/tests/run/quote-inline-function/quoted_1.scala b/tests/run/quote-inline-function/quoted_1.scala index 55dcc04efe8a..58f41a423995 100644 --- a/tests/run/quote-inline-function/quoted_1.scala +++ b/tests/run/quote-inline-function/quoted_1.scala @@ -4,8 +4,8 @@ import dotty.tools.dotc.quoted.Toolbox._ object Macros { - inline def foreach1(start: Int, end: Int, f: Int => Unit): String = ~impl('(start), '(end), '(f)) - inline def foreach2(start: Int, end: Int, f: => Int => Unit): String = ~impl('(start), '(end), '(f)) + transparent def foreach1(start: Int, end: Int, f: Int => Unit): String = ~impl('(start), '(end), '(f)) + transparent def foreach2(start: Int, end: Int, f: => Int => Unit): String = ~impl('(start), '(end), '(f)) def impl(start: Expr[Int], end: Expr[Int], f: Expr[Int => Unit]): Expr[String] = { val res = '{ diff --git a/tests/run/quote-sep-comp/Macro_1.scala b/tests/run/quote-sep-comp/Macro_1.scala index c3ce7e2e1d53..5f7e539c4230 100644 --- a/tests/run/quote-sep-comp/Macro_1.scala +++ b/tests/run/quote-sep-comp/Macro_1.scala @@ -1,5 +1,5 @@ import scala.quoted._ object Macros { - inline def assert2(expr: => Boolean): Unit = ~ assertImpl('(expr)) + transparent def assert2(expr: => Boolean): Unit = ~ assertImpl('(expr)) def assertImpl(expr: Expr[Boolean]) = '{ println(~expr) } } diff --git a/tests/run/quote-simple-macro/quoted_1.scala b/tests/run/quote-simple-macro/quoted_1.scala index 165889e278ee..7d1549148650 100644 --- a/tests/run/quote-simple-macro/quoted_1.scala +++ b/tests/run/quote-simple-macro/quoted_1.scala @@ -1,6 +1,6 @@ import scala.quoted._ object Macros { - inline def foo(inline i: Int, dummy: Int, j: Int): Int = ~bar(i + 1, '(j)) + transparent def foo(transparent i: Int, dummy: Int, j: Int): Int = ~bar(i + 1, '(j)) def bar(x: Int, y: Expr[Int]): Expr[Int] = '{ ~x.toExpr + ~y } } diff --git a/tests/run/quote-splice-interpret-1/Macro_1.scala b/tests/run/quote-splice-interpret-1/Macro_1.scala index 6ba8f0259e4e..16a12855bc8f 100644 --- a/tests/run/quote-splice-interpret-1/Macro_1.scala +++ b/tests/run/quote-splice-interpret-1/Macro_1.scala @@ -7,7 +7,7 @@ object Macros { case object Z extends Nat case class S[N <: Nat]() extends Nat - inline def isZero(inline n: Int): Boolean = ~{ + transparent def isZero(transparent n: Int): Boolean = ~{ if (n == 0) '(true) else '(false) } diff --git a/tests/run/quote-unrolled-foreach/quoted_1.scala b/tests/run/quote-unrolled-foreach/quoted_1.scala index 0f1ac1d3599a..f5a8b5ee5049 100644 --- a/tests/run/quote-unrolled-foreach/quoted_1.scala +++ b/tests/run/quote-unrolled-foreach/quoted_1.scala @@ -3,7 +3,7 @@ import scala.quoted._ object Macro { - inline def unrolledForeach(inline unrollSize: Int, seq: Array[Int])(f: => Int => Unit): Unit = // or f: Int => Unit + transparent def unrolledForeach(transparent unrollSize: Int, seq: Array[Int])(f: => Int => Unit): Unit = // or f: Int => Unit ~unrolledForeachImpl(unrollSize, '(seq), '(f)) private def unrolledForeachImpl(unrollSize: Int, seq: Expr[Array[Int]], f: Expr[Int => Unit]): Expr[Unit] = '{ diff --git a/tests/run/reduce-projections.check b/tests/run/reduce-projections.check new file mode 100644 index 000000000000..b09296cec78b --- /dev/null +++ b/tests/run/reduce-projections.check @@ -0,0 +1,59 @@ +1 +2 +3 +4 +1 +1 +2 +3 +4 +2 +1 +2 +3 +4 +3 +1 +2 +3 +4 +4 +=== +2 +3 +4 +1 +1 +3 +4 +2 +1 +2 +4 +3 +1 +2 +3 +4 +=== +2 +3 +1 +3 +2 +2 +3 +2 +3 +4 +=== +2 +3 +1 +3 +2 +2 +3 +2 +3 +4 diff --git a/tests/run/reduce-projections.scala b/tests/run/reduce-projections.scala new file mode 100644 index 000000000000..fc4099100404 --- /dev/null +++ b/tests/run/reduce-projections.scala @@ -0,0 +1,122 @@ + +class C(val x1: Int, val x2: Int, val x3: Int, val x4: Int) + +object Test { + + class D(n: Int) { + println(n) + def result = n + } + object O2 extends D(2) + object O2a extends D(2) + object O2b extends D(2) + object O3 extends D(3) + object O3a extends D(3) + object O3b extends D(3) + + transparent def f(): Unit = { + println(new C( + { println(1); 1 }, + { println(2); 2 }, + { println(3); 3 }, + { println(4); 4 } + ).x1) + println(new C( + { println(1); 1 }, + { println(2); 2 }, + { println(3); 3 }, + { println(4); 4 } + ).x2) + println(new C( + { println(1); 1 }, + { println(2); 2 }, + { println(3); 3 }, + { println(4); 4 } + ).x3) + println(new C( + { println(1); 1 }, + { println(2); 2 }, + { println(3); 3 }, + { println(4); 4 } + ).x4) + println("===") + println(new C( + { 1 }, + { println(2); 2 }, + { println(3); 3 }, + { println(4); 4 } + ).x1) + println(new C( + { println(1); 1 }, + { 2 }, + { println(3); 3 }, + { println(4); 4 } + ).x2) + println(new C( + { println(1); 1 }, + { println(2); 2 }, + { 3 }, + { println(4); 4 } + ).x3) + println(new C( + { println(1); 1 }, + { println(2); 2 }, + { println(3); 3 }, + { 4 } + ).x4) + println("===") + println(new C( + { 1 }, + { println(2); 2 }, + { println(3); 3 }, + { 4 } + ).x1) + println(new C( + { 1 }, + { 2 }, + { println(3); 3 }, + { 4 } + ).x2) + println(new C( + { 1 }, + { println(2); 2 }, + { 3 }, + { 4 } + ).x3) + println(new C( + { 1 }, + { println(2); 2 }, + { println(3); 3 }, + { 4 } + ).x4) + println("===") + println(new C( + { 1 }, + O2.result, + O3.result, + { 4 } + ).x1) + println(new C( + { 1 }, + { 2 }, + O3a.result, + { 4 } + ).x2) + println(new C( + { 1 }, + O2a.result, + { 3 }, + { 4 } + ).x3) + println(new C( + { 1 }, + O2b.result, + O3b.result, + { 4 } + ).x4) + } + + def main(args: Array[String]): Unit = { + f() + } +} \ No newline at end of file diff --git a/tests/run/tasty-custom-show/quoted_1.scala b/tests/run/tasty-custom-show/quoted_1.scala index 59ee8806a5e5..dfadb7e83e37 100644 --- a/tests/run/tasty-custom-show/quoted_1.scala +++ b/tests/run/tasty-custom-show/quoted_1.scala @@ -6,7 +6,7 @@ import scala.tasty.util.{TreeTraverser, Show} object Macros { - implicit inline def printOwners[T](x: => T): Unit = + implicit transparent def printOwners[T](x: => T): Unit = ~impl('(x))(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl[T](x: Expr[T])(implicit tasty: Tasty): Expr[Unit] = { diff --git a/tests/run/tasty-eval/quoted_1.scala b/tests/run/tasty-eval/quoted_1.scala index 0b4e4761607b..dada0c5925f9 100644 --- a/tests/run/tasty-eval/quoted_1.scala +++ b/tests/run/tasty-eval/quoted_1.scala @@ -5,14 +5,14 @@ import scala.tasty.util.TreeTraverser object Macros { - implicit inline def foo(i: Int): String = + implicit transparent def foo(i: Int): String = ~impl('(i))(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl(i: Expr[Int])(implicit tasty: Tasty): Expr[String] = { value(i).toString.toExpr } - inline implicit def value[X](e: Expr[X])(implicit tasty: Tasty, ev: Valuable[X]): Option[X] = ev.value(e) + transparent implicit def value[X](e: Expr[X])(implicit tasty: Tasty, ev: Valuable[X]): Option[X] = ev.value(e) trait Valuable[X] { def value(e: Expr[X])(implicit tasty: Tasty): Option[X] diff --git a/tests/run/tasty-extractors-1/quoted_1.scala b/tests/run/tasty-extractors-1/quoted_1.scala index 3bbf1d01924e..a04873a208cb 100644 --- a/tests/run/tasty-extractors-1/quoted_1.scala +++ b/tests/run/tasty-extractors-1/quoted_1.scala @@ -4,7 +4,7 @@ import scala.tasty._ object Macros { - implicit inline def printTree[T](x: => T): Unit = + implicit transparent def printTree[T](x: => T): Unit = ~impl('(x))(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl[T](x: Expr[T])(implicit tasty: Tasty): Expr[Unit] = { diff --git a/tests/run/tasty-extractors-2.check b/tests/run/tasty-extractors-2.check index 2baf5ba3e382..582c6e072cb8 100644 --- a/tests/run/tasty-extractors-2.check +++ b/tests/run/tasty-extractors-2.check @@ -91,7 +91,7 @@ Type.SymRef(ClassDef("Unit", _, _, _, _), Type.ThisType(Type.SymRef(PackageDef(" Term.Block(List(ClassDef("Foo12", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Synthetic()), "", Some(Signature(Nil, java.lang.Object))), Nil)), None, List(ValDef("a", TypeTree.Synthetic(), Some(Term.Literal(Constant.Int(11))))))), Term.Literal(Constant.Unit())) Type.SymRef(ClassDef("Unit", _, _, _, _), Type.ThisType(Type.SymRef(PackageDef("scala", _), NoPrefix()))) -Term.Block(List(ClassDef("Foo", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Synthetic()), "", Some(Signature(Nil, java.lang.Object))), Nil)), None, Nil), ClassDef("Bar", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Synthetic()), "", Some(Signature(Nil, Test$._$Foo))), Nil)), None, Nil)), Term.Literal(Constant.Unit())) +Term.Block(List(ClassDef("Foo", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Synthetic()), "", Some(Signature(Nil, java.lang.Object))), Nil)), None, Nil), ClassDef("Bar", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.TypeIdent("Foo")), "", Some(Signature(Nil, Test$._$Foo))), Nil)), None, Nil)), Term.Literal(Constant.Unit())) Type.SymRef(ClassDef("Unit", _, _, _, _), Type.ThisType(Type.SymRef(PackageDef("scala", _), NoPrefix()))) Term.Block(List(ClassDef("Foo2", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(TypeTree.Synthetic()), None, Nil), ClassDef("Bar", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Synthetic()), "", Some(Signature(Nil, java.lang.Object))), Nil), TypeTree.TypeIdent("Foo2")), None, Nil)), Term.Literal(Constant.Unit())) diff --git a/tests/run/tasty-extractors-2/quoted_1.scala b/tests/run/tasty-extractors-2/quoted_1.scala index 49d6c5493f40..b8cb7d1cccb3 100644 --- a/tests/run/tasty-extractors-2/quoted_1.scala +++ b/tests/run/tasty-extractors-2/quoted_1.scala @@ -4,7 +4,7 @@ import scala.tasty._ object Macros { - implicit inline def printTree[T](x: => T): Unit = + implicit transparent def printTree[T](x: => T): Unit = ~impl('(x))(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl[T](x: Expr[T])(implicit tasty: Tasty): Expr[Unit] = { diff --git a/tests/run/tasty-extractors-3/quoted_1.scala b/tests/run/tasty-extractors-3/quoted_1.scala index e49d7b603158..741212d1f5a3 100644 --- a/tests/run/tasty-extractors-3/quoted_1.scala +++ b/tests/run/tasty-extractors-3/quoted_1.scala @@ -6,7 +6,7 @@ import scala.tasty.util.TreeTraverser object Macros { - implicit inline def printTypes[T](x: => T): Unit = + implicit transparent def printTypes[T](x: => T): Unit = ~impl('(x))(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl[T](x: Expr[T])(implicit tasty: Tasty): Expr[Unit] = { diff --git a/tests/run/tasty-extractors-constants-1/quoted_1.scala b/tests/run/tasty-extractors-constants-1/quoted_1.scala index e26eee497303..47ebe1df8ba6 100644 --- a/tests/run/tasty-extractors-constants-1/quoted_1.scala +++ b/tests/run/tasty-extractors-constants-1/quoted_1.scala @@ -5,7 +5,7 @@ import scala.tasty.util._ object Macros { - implicit inline def testMacro: Unit = + implicit transparent def testMacro: Unit = ~impl(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl(implicit tasty: Tasty): Expr[Unit] = { diff --git a/tests/run/tasty-extractors-constants-2/quoted_1.scala b/tests/run/tasty-extractors-constants-2/quoted_1.scala index de396371fbe3..9b49afae8d00 100644 --- a/tests/run/tasty-extractors-constants-2/quoted_1.scala +++ b/tests/run/tasty-extractors-constants-2/quoted_1.scala @@ -6,7 +6,7 @@ import scala.tasty.util._ object Macros { - inline def testMacro: Unit = + transparent def testMacro: Unit = ~impl(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl(implicit tasty: Tasty): Expr[Unit] = { diff --git a/tests/run/tasty-extractors-owners.check b/tests/run/tasty-extractors-owners.check index a7c3a8e3b31b..84e8f1b6be6d 100644 --- a/tests/run/tasty-extractors-owners.check +++ b/tests/run/tasty-extractors-owners.check @@ -17,11 +17,11 @@ baz2 ValDef("foo2", TypeTree.Synthetic(), None) -ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Synthetic()), "", Some(Signature(Nil, java.lang.Object))), Nil)), None, List(TypeDef("B", SyntheticBounds()), DefDef("b", Nil, Nil, TypeTree.Synthetic(), None), ValDef("b2", TypeTree.Synthetic(), None))) +ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(TypeTree.Synthetic()), None, List(TypeDef("B", SyntheticBounds()), DefDef("b", Nil, Nil, TypeTree.Synthetic(), None), ValDef("b2", TypeTree.Synthetic(), None))) b -ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Synthetic()), "", Some(Signature(Nil, java.lang.Object))), Nil)), None, List(TypeDef("B", SyntheticBounds()), DefDef("b", Nil, Nil, TypeTree.Synthetic(), None), ValDef("b2", TypeTree.Synthetic(), None))) +ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(TypeTree.Synthetic()), None, List(TypeDef("B", SyntheticBounds()), DefDef("b", Nil, Nil, TypeTree.Synthetic(), None), ValDef("b2", TypeTree.Synthetic(), None))) b2 -ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Synthetic()), "", Some(Signature(Nil, java.lang.Object))), Nil)), None, List(TypeDef("B", SyntheticBounds()), DefDef("b", Nil, Nil, TypeTree.Synthetic(), None), ValDef("b2", TypeTree.Synthetic(), None))) +ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(TypeTree.Synthetic()), None, List(TypeDef("B", SyntheticBounds()), DefDef("b", Nil, Nil, TypeTree.Synthetic(), None), ValDef("b2", TypeTree.Synthetic(), None))) diff --git a/tests/run/tasty-extractors-owners/quoted_1.scala b/tests/run/tasty-extractors-owners/quoted_1.scala index 5afeb121c448..dacacdc05a2b 100644 --- a/tests/run/tasty-extractors-owners/quoted_1.scala +++ b/tests/run/tasty-extractors-owners/quoted_1.scala @@ -5,7 +5,7 @@ import scala.tasty.util.TreeTraverser object Macros { - implicit inline def printOwners[T](x: => T): Unit = + implicit transparent def printOwners[T](x: => T): Unit = ~impl('(x))(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl[T](x: Expr[T])(implicit tasty: Tasty): Expr[Unit] = { diff --git a/tests/run/tasty-extractors-types/quoted_1.scala b/tests/run/tasty-extractors-types/quoted_1.scala index 3d8f4cfe553e..2b5b80112bc0 100644 --- a/tests/run/tasty-extractors-types/quoted_1.scala +++ b/tests/run/tasty-extractors-types/quoted_1.scala @@ -5,7 +5,7 @@ import scala.tasty.util.TreeTraverser object Macros { - implicit inline def printType[T]: Unit = + implicit transparent def printType[T]: Unit = ~impl('[T])(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl[T](x: Type[T])(implicit tasty: Tasty): Expr[Unit] = { diff --git a/tests/run/tasty-getfile/Macro_1.scala b/tests/run/tasty-getfile/Macro_1.scala index 867695307433..57c10cdb3909 100644 --- a/tests/run/tasty-getfile/Macro_1.scala +++ b/tests/run/tasty-getfile/Macro_1.scala @@ -3,7 +3,7 @@ import scala.tasty.{Tasty, TopLevelSplice} object SourceFiles { - implicit inline def getThisFile: String = + implicit transparent def getThisFile: String = ~getThisFileImpl(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ private def getThisFileImpl(implicit tasty: Tasty): Expr[String] = { diff --git a/tests/run/tasty-indexed-map/quoted_1.scala b/tests/run/tasty-indexed-map/quoted_1.scala index 56d5559b67c9..ac66c1c4a75b 100644 --- a/tests/run/tasty-indexed-map/quoted_1.scala +++ b/tests/run/tasty-indexed-map/quoted_1.scala @@ -24,7 +24,7 @@ object Index { implicit def zero[K, T]: Index[K, (K, T)] = new Index(0) - implicit inline def succ[K, H, T](implicit prev: => Index[K, T]): Index[K, (H, T)] = ~succImpl(TopLevelSplice.tastyContext)('[K], '[H], '[T]) + implicit transparent def succ[K, H, T](implicit prev: => Index[K, T]): Index[K, (H, T)] = ~succImpl(TopLevelSplice.tastyContext)('[K], '[H], '[T]) def succImpl[K, H, T](tasty: Tasty)(implicit k: Type[K], h: Type[H], t: Type[T]): Expr[Index[K, (H, T)]] = { import tasty._ diff --git a/tests/run/tasty-linenumber/quoted_1.scala b/tests/run/tasty-linenumber/quoted_1.scala index 01cb1e7efb5f..09a42088f6e3 100644 --- a/tests/run/tasty-linenumber/quoted_1.scala +++ b/tests/run/tasty-linenumber/quoted_1.scala @@ -8,7 +8,7 @@ class LineNumber(val value: Int) { object LineNumber { - implicit inline def line[T >: Unit <: Unit]: LineNumber = + implicit transparent def line[T >: Unit <: Unit]: LineNumber = ~lineImpl('[T])(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def lineImpl(x: Type[Unit])(implicit tasty: Tasty): Expr[LineNumber] = { diff --git a/tests/run/tasty-location/quoted_1.scala b/tests/run/tasty-location/quoted_1.scala index 96bdc2439944..ccb088ab1c66 100644 --- a/tests/run/tasty-location/quoted_1.scala +++ b/tests/run/tasty-location/quoted_1.scala @@ -6,7 +6,7 @@ case class Location(owners: List[String]) object Location { - implicit inline def location: Location = + implicit transparent def location: Location = ~impl(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl(implicit tasty: Tasty): Expr[Location] = { diff --git a/tests/run/tasty-macro-assert/quoted_1.scala b/tests/run/tasty-macro-assert/quoted_1.scala index 36a83c0106f4..2a5dd386119b 100644 --- a/tests/run/tasty-macro-assert/quoted_1.scala +++ b/tests/run/tasty-macro-assert/quoted_1.scala @@ -11,7 +11,7 @@ object Asserts { object Ops - inline def macroAssert(cond: Boolean): Unit = + transparent def macroAssert(cond: Boolean): Unit = ~impl('(cond))(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl(cond: Expr[Boolean])(implicit tasty: Tasty): Expr[Unit] = { diff --git a/tests/run/tasty-positioned/quoted_1.scala b/tests/run/tasty-positioned/quoted_1.scala index b1536bc1fae2..7f4ecc09b701 100644 --- a/tests/run/tasty-positioned/quoted_1.scala +++ b/tests/run/tasty-positioned/quoted_1.scala @@ -9,7 +9,7 @@ case class Positioned[T](value: T, position: Position) object Positioned { - implicit inline def apply[T](x: T): Positioned[T] = + implicit transparent def apply[T](x: T): Positioned[T] = ~impl('(x))('[T], TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl[T](x: Expr[T])(implicit ev: Type[T], tasty: Tasty): Expr[Positioned[T]] = { diff --git a/tests/run/transparent-foreach.scala b/tests/run/transparent-foreach.scala new file mode 100644 index 000000000000..a13134f4047c --- /dev/null +++ b/tests/run/transparent-foreach.scala @@ -0,0 +1,70 @@ +// A demonstrator how `transparent override` can be used to sepcialize inherited methods +trait Iterable[+A] { + def foreach(f: A => Unit): Unit +} +// The unconventional definition of `Range` and `UntilRange` is needed so +// that `UntiRange` is recognized as a Noinits class. Our purity predictor +// for classes currently bails out for arguments passed to parent classes +// and for concrete `val`s. That's because the predictor runs on untyped trees +// so there's no way to predict whether an expression is pure or not. +// It would be nice if we could improve the predictor to work with typed trees. +// The tricky bit is doing this without causing cycles. That's the price we +// pay for making inlining a typelevel computation. +// One possible way to do it would be to make purity a property that's computed on demand, +// just like info, but evaluated later. Then we might still cause cycles, but these +// would be "justified" by inlining attempts. I.e. you could avoid a cycle by +// inlining less. +abstract class Range extends Iterable[Int] { + val start: Int + val end: Int + def step: Int + def inclusive: Boolean + def foreach(f: Int => Unit): Unit = { + var idx = start + while ( + if (step > 0) + if (inclusive) idx <= end else idx < end + else + if (inclusive) idx >= end else idx > end + ) { + f(idx) + idx = idx + step + } + } +} +class UntilRange(val start: Int, val end: Int) extends Range { + def step = 1 + def inclusive = false + transparent override def foreach(f: Int => Unit): Unit = { + var idx = start + while (idx < end) { + f(idx) + idx += 1 + } + } +} +object Test extends App { + var x = 0 + new UntilRange(1, 10).foreach(x += _) + // Expands to: + // var idx: Int = 1 + // while idx < 10 do + // x = x * idx + // idx = idx + 1 + // } + + class IntDeco(val x: Int) extends AnyVal { + transparent def until(y: Int) = new UntilRange(x, y) + } + implicit transparent def intDeco(x: Int): IntDeco = new IntDeco(x) + // So far, the decorator has to be an explicit def, since + // we can inline only methods defined in source. So an implicit class + // will not work. We can make this work by making `Desugar` smarter + // and generate a transparent with pre-defined "BodyToInline" annotation. + // It's doable, just extra work. + + (1 until 10).foreach(x += _) + // expands to same as above + + assert(x == 90) +} diff --git a/tests/run/inline-implicits.check b/tests/run/transparent-implicits.check similarity index 100% rename from tests/run/inline-implicits.check rename to tests/run/transparent-implicits.check diff --git a/tests/run/inline-implicits.scala b/tests/run/transparent-implicits.scala similarity index 95% rename from tests/run/inline-implicits.scala rename to tests/run/transparent-implicits.scala index 6d46a61d28e4..bd41c41eecb9 100644 --- a/tests/run/inline-implicits.scala +++ b/tests/run/transparent-implicits.scala @@ -11,7 +11,7 @@ object inlines { class C { implicit val x: X = new X() - inline + transparent def f(): (X, Y) = (implicitly[X], implicitly[Y]) } diff --git a/tests/run/inline-object.check b/tests/run/transparent-object.check similarity index 100% rename from tests/run/inline-object.check rename to tests/run/transparent-object.check diff --git a/tests/run/inline-object.scala b/tests/run/transparent-object.scala similarity index 81% rename from tests/run/inline-object.scala rename to tests/run/transparent-object.scala index 88a5777dd252..21d2566022df 100644 --- a/tests/run/inline-object.scala +++ b/tests/run/transparent-object.scala @@ -6,7 +6,7 @@ object Test { } object Foo extends Bar { - inline def foo: Unit = bar + transparent def foo: Unit = bar } class Bar { diff --git a/tests/run/inline.check b/tests/run/transparent.check similarity index 100% rename from tests/run/inline.check rename to tests/run/transparent.check diff --git a/tests/run/inline/Test_2.scala b/tests/run/transparent/Test_2.scala similarity index 92% rename from tests/run/inline/Test_2.scala rename to tests/run/transparent/Test_2.scala index 431951300eb6..722e302c4b26 100644 --- a/tests/run/inline/Test_2.scala +++ b/tests/run/transparent/Test_2.scala @@ -1,6 +1,6 @@ object Test { - import p.inlines._ + import p.transparents._ def main(args: Array[String]): Unit = { println(f(10)) @@ -10,14 +10,17 @@ object Test { val o = new Outer val i = new o.Inner - val p = new TestPassing println(i.m) println(i.g) println(i.h) println(o.inner.m) println(o.inner.g) println(o.inner.h) + + val p = new TestPassing + println(p.foo("hi")) println(p.bar(true)) + } -} \ No newline at end of file +} diff --git a/tests/run/inline/inlines_1.scala b/tests/run/transparent/inlines_1.scala similarity index 63% rename from tests/run/inline/inlines_1.scala rename to tests/run/transparent/inlines_1.scala index 825d84913013..7fa71ba47ab9 100644 --- a/tests/run/inline/inlines_1.scala +++ b/tests/run/transparent/inlines_1.scala @@ -1,11 +1,10 @@ package p import collection.mutable -object inlines { - +object transparents { final val monitored = false - inline def f(x: Int): Int = x * x + transparent def f(x: Int): Int = x * x val hits = new mutable.HashMap[String, Int] { override def default(key: String): Int = 0 @@ -20,7 +19,7 @@ object inlines { @volatile private var stack: List[String] = Nil - inline def track[T](fn: String)(op: => T) = + transparent def track[T](fn: String)(op: => T) = if (monitored) { stack = fn :: stack record(fn) @@ -32,25 +31,25 @@ object inlines { def f = "Outer.f" class Inner { val msg = " Inner" - inline def m = msg - inline def g = f - inline def h = f ++ m + transparent def m = msg + transparent def g = f + transparent def h = f ++ m } val inner = new Inner } - class C[T](private[inlines] val x: T) { - private[inlines] def next[U](y: U): (T, U) = (xx, y) - private[inlines] var xx: T = _ + class C[T](private[transparents] val x: T) { + private[transparents] def next[U](y: U): (T, U) = (xx, y) + private[transparents] var xx: T = _ } class TestPassing { - inline def foo[A](x: A): (A, Int) = { + transparent def foo[A](x: A): (A, Int) = { val c = new C[A](x) c.xx = c.x c.next(1) } - inline def bar[A](x: A): (A, String) = { + transparent def bar[A](x: A): (A, String) = { val c = new C[A](x) c.xx = c.x c.next("") diff --git a/tests/run/transparentAccess/C_1.scala b/tests/run/transparentAccess/C_1.scala new file mode 100644 index 000000000000..45db8603e8c6 --- /dev/null +++ b/tests/run/transparentAccess/C_1.scala @@ -0,0 +1,7 @@ +package p { +class C { + protected def f(): Unit = () + + transparent def inl() = f() // error (when inlined): not accessible +} +} diff --git a/tests/run/inlineAccess/Test_2.scala b/tests/run/transparentAccess/Test_2.scala similarity index 100% rename from tests/run/inlineAccess/Test_2.scala rename to tests/run/transparentAccess/Test_2.scala diff --git a/tests/run/inlineArrowAssoc.scala b/tests/run/transparentArrowAssoc.scala similarity index 88% rename from tests/run/inlineArrowAssoc.scala rename to tests/run/transparentArrowAssoc.scala index 0a3b5c6d7e37..80dd0d90d3c3 100644 --- a/tests/run/inlineArrowAssoc.scala +++ b/tests/run/transparentArrowAssoc.scala @@ -10,7 +10,7 @@ object Test { ) final implicit class ArrowAssoc[A](private val self: A) extends AnyVal { - inline def -> [B](y: B): Tuple2[A, B] = Tuple2(self, y) + transparent def -> [B](y: B): Tuple2[A, B] = Tuple2(self, y) def →[B](y: B): Tuple2[A, B] = ->(y) } diff --git a/tests/run/transparentAssign.scala b/tests/run/transparentAssign.scala new file mode 100644 index 000000000000..5851550f857f --- /dev/null +++ b/tests/run/transparentAssign.scala @@ -0,0 +1,24 @@ +object Test { + + transparent def swap[T](x: T, x_= : => T => Unit, y: T, y_= : => T => Unit) = { + x_=(y) + y_=(x) + } + + transparent def f(x: Int => Unit) = x + + def main(args: Array[String]) = { + var x = 1 + var y = 2 + def setX(z: Int) = x = z + def setY(z: Int) = y = z + swap(x, setX, y, setY) + assert(x == 2 && y == 1) + + swap(x, x = _, y, y = _) + assert(x == 1 && y == 2) + + + val z = f(setX) // tests case where transparent arg is not applied + } +} diff --git a/tests/run/inlineByName.scala b/tests/run/transparentByName.scala similarity index 69% rename from tests/run/inlineByName.scala rename to tests/run/transparentByName.scala index 94f276dcce3e..837ecc7ff26a 100644 --- a/tests/run/inlineByName.scala +++ b/tests/run/transparentByName.scala @@ -1,7 +1,7 @@ object Test { class Range(from: Int, end: Int) { - inline def foreach(op: => Int => Unit): Unit = { + transparent def foreach(op: => Int => Unit): Unit = { var i = from while (i < end) { op(i) @@ -9,11 +9,11 @@ object Test { } } } - inline def twice(op: => Int => Unit): Unit = { + transparent def twice(op: => Int => Unit): Unit = { op(1) op(2) } - inline def thrice(op: => Unit): Unit = { + transparent def thrice(op: => Unit): Unit = { op op op @@ -25,7 +25,7 @@ object Test { assert(j == 45, j) twice { x => j = j - x } thrice { j = j + 1 } - val f = new Range(1, 10).foreach + val f = (g: Int => Unit) => new Range(1, 10).foreach(g) f(j -= _) assert(j == 0, j) new Range(1, 10).foreach { i1 => diff --git a/tests/run/inlineForeach.check b/tests/run/transparentForeach.check similarity index 100% rename from tests/run/inlineForeach.check rename to tests/run/transparentForeach.check diff --git a/tests/run/inlineForeach.scala b/tests/run/transparentForeach.scala similarity index 95% rename from tests/run/inlineForeach.scala rename to tests/run/transparentForeach.scala index 7f22f1cc3753..eda163707093 100644 --- a/tests/run/inlineForeach.scala +++ b/tests/run/transparentForeach.scala @@ -2,7 +2,7 @@ object Test { class Range(from: Int, end: Int) { - inline + transparent def foreach(op: => Int => Unit): Unit = { var i = from while (i < end) { @@ -36,7 +36,7 @@ object Test { } implicit class intArrayOps(arr: Array[Int]) { - inline def foreach(op: => Int => Unit): Unit = { + transparent def foreach(op: => Int => Unit): Unit = { var i = 0 while (i < arr.length) { op(arr(i)) diff --git a/tests/run/inlinePower.check b/tests/run/transparentPower.check similarity index 100% rename from tests/run/inlinePower.check rename to tests/run/transparentPower.check diff --git a/tests/run/inlinePower/Test_2.scala b/tests/run/transparentPower/Test_2.scala similarity index 100% rename from tests/run/inlinePower/Test_2.scala rename to tests/run/transparentPower/Test_2.scala diff --git a/tests/run/inlinePower/power_1.scala b/tests/run/transparentPower/power_1.scala similarity index 75% rename from tests/run/inlinePower/power_1.scala rename to tests/run/transparentPower/power_1.scala index 4e96d7caa264..ef43488ce387 100644 --- a/tests/run/inlinePower/power_1.scala +++ b/tests/run/transparentPower/power_1.scala @@ -2,7 +2,7 @@ package p object pow { - inline def power(x: Double, n: Int): Double = + transparent def power(x: Double, n: Int): Double = if (n == 0) 1.0 else if (n == 1) x else { diff --git a/tests/run/inlinePrivates.scala b/tests/run/transparentPrivates.scala similarity index 58% rename from tests/run/inlinePrivates.scala rename to tests/run/transparentPrivates.scala index ce438ae8d8a0..8498203af9ba 100644 --- a/tests/run/inlinePrivates.scala +++ b/tests/run/transparentPrivates.scala @@ -6,19 +6,19 @@ object Test { private var y: T = _ - inline def get1 = x - inline def get2[U](c: C[U]) = c.x + transparent def get1 = x + transparent def get2[U](c: C[U]) = c.x - inline def foo1(x: Int) = foo(x) - inline def foo2[U](c: C[U]) = c.foo(x) + transparent def foo1(x: Int) = foo(x) + transparent def foo2[U](c: C[U]) = c.foo(x) - inline def set1(z: T) = { y = z; y } - inline def set2[U](c: C[U]) = { c.y = c.x; c.y } + transparent def set1(z: T) = { y = z; y } + transparent def set2[U](c: C[U]) = { c.y = c.x; c.y } } object CC { private val x = 3 - inline def get1 = x + transparent def get1 = x } def main(args: Array[String]) = { diff --git a/tests/run/inlineProtected.scala b/tests/run/transparentProtected.scala similarity index 88% rename from tests/run/inlineProtected.scala rename to tests/run/transparentProtected.scala index 1a725ec27346..478f599b2293 100644 --- a/tests/run/inlineProtected.scala +++ b/tests/run/transparentProtected.scala @@ -7,7 +7,7 @@ package P { package Q { class D extends P.C { class Inner { - inline def g() = f() + transparent def g() = f() } } } diff --git a/tests/run/typelevel-defaultValue.check b/tests/run/typelevel-defaultValue.check new file mode 100644 index 000000000000..44b1d5b6313f Binary files /dev/null and b/tests/run/typelevel-defaultValue.check differ diff --git a/tests/run/typelevel-defaultValue.scala b/tests/run/typelevel-defaultValue.scala new file mode 100644 index 000000000000..c31b363f3b18 --- /dev/null +++ b/tests/run/typelevel-defaultValue.scala @@ -0,0 +1,40 @@ + +object typelevel { + erased def erasedValue[T]: T = ??? +} + +object Test extends App { + + transparent def defaultValue[T]: Option[Any] = typelevel.erasedValue[T] match { + case _: Byte => Some(0: Byte) + case c: Char => Some(0: Char) + case d @ (_: Short) => Some(0: Short) + case _: Int => Some(0) + case _: Long => Some(0L) + case _: Float => Some(0.0f) + case _: Double => Some(0.0d) + case _: Boolean => Some(false) + case _: Unit => Some(()) + //case _: t >: Null => Some(null) + case _ => None + } + + val dInt = defaultValue[Int] + val dDouble = defaultValue[Double] + val dBoolean = defaultValue[Boolean] + val dChar = defaultValue[Char] + val dString = defaultValue[String] + val dAny = defaultValue[Any] + println(dInt) + println(dDouble) + println(dBoolean) + println(dChar) + println(dString) + println(dAny) + val cInt: Int = dInt.get + val cDouble: Double = dDouble.get + val cBoolean: Boolean = dBoolean.get + val cChar: Char = dChar.get + assert(dString.isEmpty) + assert(dAny.isEmpty) +} \ No newline at end of file diff --git a/tests/run/typelevel-numeric.check b/tests/run/typelevel-numeric.check new file mode 100644 index 000000000000..18e16e38c5de --- /dev/null +++ b/tests/run/typelevel-numeric.check @@ -0,0 +1 @@ +-1.0 diff --git a/tests/run/typelevel-numeric.scala b/tests/run/typelevel-numeric.scala new file mode 100644 index 000000000000..7934f961722f --- /dev/null +++ b/tests/run/typelevel-numeric.scala @@ -0,0 +1,33 @@ + +import math.Numeric + +abstract class MathLib[N : Numeric] { + def dotProduct(xs: Array[N], ys: Array[N]): N +} + +object MathLib { + + transparent def apply[N](implicit n: Numeric[N]) = new MathLib[N] { + import n._ + def dotProduct(xs: Array[N], ys: Array[N]): N = { + require(xs.length == ys.length) + var i = 0 + var s: N = n.zero + while (i < xs.length) { + s = s + xs(i) * ys(i) + i += 1 + } + s + } + } +} + +object Test extends App { + + val mlib = MathLib.apply[Double](scala.math.Numeric.DoubleIsFractional) + + val xs = Array(1.0, 1.0) + val ys = Array(2.0, -3.0) + val p = mlib.dotProduct(xs, ys) + println(p) +} diff --git a/tests/run/typelevel-overrides.scala b/tests/run/typelevel-overrides.scala new file mode 100644 index 000000000000..67a9512048f5 --- /dev/null +++ b/tests/run/typelevel-overrides.scala @@ -0,0 +1,29 @@ +trait T { + def f(x: Int): Int +} +trait U extends T { + override def f(x: Int): Int = x + 1 +} +class A extends T { + def f(x: Int) = x +} +class B extends A { + override transparent def f(x: Int) = x match { + case 0 => 0 + case x => x + } +} +class C extends A with U { + override transparent def f(x: Int) = x match { + case 0 => 0 + case x => x + } +} +object Test extends App { + val a: A = new B + assert(a.f(0) == 0) + val b: B = new B + assert(b.f(0) == 0) + val c: A = new C + assert(c.f(0) == 1, c.f(0)) +} \ No newline at end of file diff --git a/tests/run/typelevel-patmat.check b/tests/run/typelevel-patmat.check new file mode 100644 index 000000000000..295138eb77dc --- /dev/null +++ b/tests/run/typelevel-patmat.check @@ -0,0 +1,3 @@ +Typed(Z) +Typed(S(Z)) +Typed(S(S(Z))) diff --git a/tests/run/typelevel-patmat.scala b/tests/run/typelevel-patmat.scala new file mode 100644 index 000000000000..6bacf57e2037 --- /dev/null +++ b/tests/run/typelevel-patmat.scala @@ -0,0 +1,108 @@ + +object typelevel { + case class Typed[T](value: T) { type Type = T } +} + +trait Nat +case object Z extends Nat +case class S[N <: Nat](n: N) extends Nat + +trait HList + +// () +case object HNil extends HList +// (H, T) + +@annotation.showAsInfix(true) +case class HCons [H, T <: HList](hd: H, tl: T) extends HList + +object Test extends App { + import typelevel._ + type HNil = HNil.type + type Z = Z.type + + transparent def ToNat(transparent n: Int): Typed[Nat] = + if n == 0 then Typed(Z) + else Typed(S(ToNat(n - 1).value)) + + val x0 = ToNat(0) + val y0: Z = x0.value + val x1 = ToNat(1) + val y1: S[Z] = x1.value + val x2 = ToNat(2) + val y2: S[S[Z]] = x2.value + println(x0) + println(x1) + println(x2) + + transparent def toInt(n: Nat): Int = n match { + case Z => 0 + case S(n1) => toInt(n1) + 1 + } + + transparent val i0 = toInt(y0) + val j0: 0 = i0 + transparent val i2 = toInt(y2) + val j2: 2 = i2 + + transparent def concat(xs: HList, ys: HList): HList = xs match { + case HNil => ys + case HCons(x, xs1) => HCons(x, concat(xs1, ys)) + } + + val xs = HCons(1, HCons("a", HNil)) + + val r0 = concat(HNil, HNil) + val c0: HNil = r0 + val r1 = concat(HNil, xs) + val c1: HCons[Int, HCons[String, HNil]] = r1 + val r2 = concat(xs, HNil) + val c2: HCons[Int, HCons[String, HNil]] = r2 + val r3 = concat(xs, xs) + val c3: HCons[Int, HCons[String, HCons[Int, HCons[String, HNil]]]] = r3 + + val r4 = concat(HNil, HCons(1, HCons("a", HNil))) + val c4: HCons[Int, HCons[String, HNil]] = r4 + val r5 = concat(HCons(1, HCons("a", HNil)) , HNil) + val c5: HCons[Int, HCons[String, HNil]] = r5 + val r6 = concat(HCons(1, HCons("a", HNil)), HCons(true, HCons(1.0, HNil))) + val c6: HCons[Int, HCons[String, HCons[Boolean, HCons[Double, HNil]]]] = r6 + + transparent def nth(xs: HList, n: Int): Any = xs match { + case HCons(x, _) if n == 0 => x + case HCons(_, xs1) if n > 0 => nth(xs1, n - 1) + } + + val e0 = nth(r2, 0) + val ce0: Int = e0 + val e1 = nth(r2, 1) + val ce1: String = e1 + + transparent def concatTyped(xs: HList, ys: HList): Typed[_ <: HList] = xs match { + case HNil => Typed(ys) + case HCons(x, xs1) => Typed(HCons(x, concatTyped(xs1, ys).value)) + } + + def concatImpl(xs: HList, ys: HList): HList = xs match { + case HNil => ys + case HCons(x, xs1) => HCons(x, concatImpl(xs1, ys)) + } + + transparent def concatErased(xs: HList, ys: HList): HList = { + erased val r = concatTyped(xs, ys) + concatImpl(xs, ys).asInstanceOf[r.Type] + } + + { + val r0 = concatErased(HNil, HNil) + val c0: HNil = r0 + val r1 = concatErased(HNil, xs) + val c1: HCons[Int, HCons[String, HNil]] = r1 + val r2 = concatErased(xs, HNil) + val c2: HCons[Int, HCons[String, HNil]] = r2 + val r3 = concatErased(xs, xs) + val c3: HCons[Int, HCons[String, HCons[Int, HCons[String, HNil]]]] = r3 + } + + +} \ No newline at end of file diff --git a/tests/run/typelevel.scala b/tests/run/typelevel.scala index 9a836565ef51..ee7bf50521f2 100644 --- a/tests/run/typelevel.scala +++ b/tests/run/typelevel.scala @@ -1,31 +1,161 @@ -object Test extends App { - trait Nat +trait Nat { + def toInt: Int = ??? +} - case object Z extends Nat - type Z = Z.type - case class S[N <: Nat](n: Nat) extends Nat +case object Z extends Nat { + transparent override def toInt = 0 +} + +case class S[N <: Nat](n: N) extends Nat { + transparent override def toInt = n.toInt + 1 +} + +trait HList { + def length: Int = ??? + def head: Any + def tail: HList + transparent def isEmpty: Boolean = + length == 0 +} - abstract class HasResult[T] { type Result = T } - case class ToNat[+T](val value: T) extends HasResult[T] +// () +case object HNil extends HList { + transparent override def length = 0 + def head: Nothing = ??? + def tail: Nothing = ??? +} - transparent def ToNat(inline n: Int): ToNat[Nat] = +// (H, T) +@annotation.showAsInfix(true) +case class HCons [H, T <: HList](hd: H, tl: T) extends HList { + transparent override def length = 1 + tl.length + def head: H = this.hd + def tail: T = this.tl +} + +case class ToNat[T](val value: T) { + type Result = T +} + +object Test extends App { + type HNil = HNil.type + type Z = Z.type + + transparent def ToNat(transparent n: Int): ToNat[Nat] = if n == 0 then new ToNat(Z) else { val n1 = ToNat(n - 1) - new ToNat[S[n1.Result]](S(n1.value)) + new ToNat(S(n1.value)) } val x0 = ToNat(0) - //val y0: Z = x0.value - //val z0: x0.Result = y0 + val y0: Z = x0.value + val z0: x0.Result = y0 val x1 = ToNat(1) - //val y1: S[Z] = x1.value - //val z1: x1.Result = y1 + val y1: S[Z] = x1.value + val z1: x1.Result = y1 val x2 = ToNat(2) - //val y2: S[S[Z]] = x2.value - //val z2: x2.Result = y2 + val y2: S[S[Z]] = x2.value + val z2: x2.Result = y2 println(x0) println(x1) println(x2) + transparent val i0 = y0.toInt + val j0: 0 = i0 + transparent val i2 = y2.toInt + val j2: 2 = i2 + + class HListDeco(private val as: HList) extends AnyVal { + transparent def :: [H] (a: H) = HCons(a, as) + transparent def ++ (bs: HList) = concat(as, bs) + } + transparent def concat(xs: HList, ys: HList): HList = + if xs.isEmpty then ys + else HCons(xs.head, concat(xs.tail, ys)) + + val xs = HCons(1, HCons("a", HNil)) + + val r0 = concat(HNil, HNil) + val r1 = concat(HNil, xs) + val r2 = concat(xs, HNil) + val r3 = concat(xs, xs) + + val r4 = concat(HNil, HCons(1, HCons("a", HNil))) + val r5 = concat(HCons(1, HCons("a", HNil)) , HNil) + val r6 = concat(HCons(1, HCons("a", HNil)), HCons(true, HCons(1.0, HNil))) + + transparent def size(xs: HList): Nat = + if xs.isEmpty then Z + else S(size(xs.tail)) + + transparent def sizeDefensive(xs: HList): Nat = xs.isEmpty match { + case true => Z + case false => S(sizeDefensive(xs.tail)) + } + + val s0 = size(HNil) + val s1 = size(xs) + transparent val l0 = HNil.length + val l0a: 0 = l0 + transparent val l1 = xs.length + val l1a: 2 = l1 + + transparent def index(xs: HList, transparent idx: Int): Any = + if idx == 0 then xs.head + else index(xs.tail, idx - 1) + + val s2 = index(xs, 0) + val ss2: Int = s2 + val s3 = index(xs, 1) + var ss3: String = s3 + def s4 = index(xs, 2) + def ss4: Nothing = s4 + val s5 = index(xs, xs.length - 1) + val ss5: String = s5 + + + //val ys = 1 :: "a" :: HNil + + transparent implicit def hlistDeco(xs: HList): HListDeco = new HListDeco(xs) + + val rr0 = new HListDeco(HNil).++(HNil) + val rr1 = HNil ++ xs + val rr2 = xs ++ HNil + val rr3 = xs ++ xs + val rr3a: HCons[Int, HCons[String, HCons[Int, HCons[String, HNil]]]] = rr3 + + transparent def f(c: Boolean): Nat = { + def g[X <: Nat](x: X): X = x + g(if (c) Z else S(Z)) + } + + val f1: Z = f(true) + val f2: S[Z] = f(false) + + transparent def mapHead[T, R](t: T)(implicit fh: T => R): R = fh(t) + transparent def map(xs: HList): HList = { + + if (xs.isEmpty) HNil + else HCons(mapHead(xs.head), map(xs.tail)) + } + + implicit def mapInt: Int => Boolean = (i: Int) => i < 23 + implicit val mapString: String => Int = (s: String) => s.length + implicit val mapBoolean: Boolean => String = (b: Boolean) => if(b) "yes" else "no" + + val res = map(HCons(23, HCons("foo", HCons(true, HNil)))) + val res1: Boolean `HCons` (Int `HCons` (String `HCons` HNil)) = res + +/* + transparent def toInt1[T]: Int = type T match { + case Z => 0 + case S[type N] => toInt[N] + 1 + } + + transparent def toInt1[T]: Nat = implicit match { + case C[type T, type U], T =:= U => + case T <:< S[type N] => toInt[N] + 1 + } +*/ } \ No newline at end of file diff --git a/tests/run/typelevel1.scala b/tests/run/typelevel1.scala new file mode 100644 index 000000000000..d6de13790965 --- /dev/null +++ b/tests/run/typelevel1.scala @@ -0,0 +1,52 @@ + +trait HList { + def length: Int = ??? + def head: Any + def tail: HList + + transparent def isEmpty: Boolean = length == 0 +} + +case object HNil extends HList { + transparent override def length = 0 + def head: Nothing = ??? + def tail: Nothing = ??? +} + +case class :: [H, T <: HList] (hd: H, tl: T) extends HList { + transparent override def length = 1 + tl.length + def head: H = this.hd + def tail: T = this.tl +} + +object Test extends App { + type HNil = HNil.type + + class HListDeco(val as: HList) extends AnyVal { + transparent def :: [H] (a: H): HList = new :: (a, as) + transparent def ++ (bs: HList): HList = concat(as, bs) + transparent def apply(idx: Int): Any = index(as, idx) + } + + transparent implicit def hlistDeco(xs: HList): HListDeco = new HListDeco(xs) + + transparent def concat[T1, T2](xs: HList, ys: HList): HList = + if xs.isEmpty then ys + else new ::(xs.head, concat(xs.tail, ys)) + + val xs = 1 :: "a" :: "b" :: HNil + val ys = true :: 1.0 :: HNil + val zs = concat(xs, ys) + + val control: Int :: String :: String :: Boolean :: Double :: HNil = zs + + transparent def index(xs: HList, idx: Int): Any = + if idx == 0 then xs.head + else index(xs.tail, idx - 1) + + val zs0 = index(zs, 0) + val zs1 = zs(1) + val zs2 = zs(2) + val zs3 = zs(3) + def zs4 = zs(4) +} \ No newline at end of file diff --git a/tests/run/typelevel3.scala b/tests/run/typelevel3.scala new file mode 100644 index 000000000000..ca115f81ad6b --- /dev/null +++ b/tests/run/typelevel3.scala @@ -0,0 +1,60 @@ + +trait HList { + def length: Int = 0 + def head: Any + def tail: HList + + transparent def isEmpty: Boolean = length == 0 +} + +case object HNil extends HList { + transparent override def length = 0 + def head: Nothing = ??? + def tail: Nothing = ??? +} + +case class HCons[H, T <: HList](hd: H, tl: T) extends HList { + transparent override def length = 1 + tl.length + def head: H = this.hd + def tail: T = this.tl +} + +case class Typed[T](val value: T) { type Type = T } + +object Test extends App { + type HNil = HNil.type + + transparent def concat(xs: HList, ys: HList): Typed[_ <: HList] = + if xs.isEmpty then Typed(ys) + else Typed(HCons(xs.head, concat(xs.tail, ys).value)) + + val xs = HCons(1, HCons("a", HCons("b", HNil))) + val ys = HCons(true, HCons(1.0, HNil)) + val zs = concat(xs, ys) + val zs1: zs.Type = zs.value + + val control: HCons[Int, HCons[String, HCons[String, HCons[Boolean, HCons[Double, HNil]]]]] = zs.value + + transparent def index(xs: HList, idx: Int): Typed[_] = + if idx == 0 then Typed(xs.head) + else Typed(index(xs.tail, idx - 1).value) + + val zsv = zs.value + val zs0 = index(zsv, 0) + val zs0c: Int = zs0.value + val zs4 = index(zsv, 4) + val zs4c: Double = zs4.value + def zs5 = index(zsv, 5) + def zs5c: Nothing = zs5.value + + def opaqueConcat(xs: HList, ys: HList): HList = + if xs.isEmpty then ys + else HCons(xs.head, opaqueConcat(xs.tail, ys)) + + transparent def compactConcat(xs: HList, ys: HList): HList = { + erased val r = concat(xs, ys) + opaqueConcat(xs, ys).asInstanceOf[r.Type] + } + + val czs = compactConcat(xs, ys) +} \ No newline at end of file diff --git a/tests/run/valueclasses-pavlov.decompiled b/tests/run/valueclasses-pavlov.decompiled index 36da557e9f4c..593489526077 100644 --- a/tests/run/valueclasses-pavlov.decompiled +++ b/tests/run/valueclasses-pavlov.decompiled @@ -8,7 +8,7 @@ final class Box1(val value: scala.Predef.String) extends scala.AnyVal() { false } } -object Box1 +object Box1 extends scala.AnyRef() /** Decompiled from out/runTestFromTasty/run/valueclasses-pavlov/Box2.class */ final class Box2(val value: scala.Predef.String) extends scala.AnyVal() with Foo { def box1(x: Box1): scala.Predef.String = "box1: ok" @@ -21,7 +21,7 @@ final class Box2(val value: scala.Predef.String) extends scala.AnyVal() with Foo false } } -object Box2 +object Box2 extends scala.AnyRef() /** Decompiled from out/runTestFromTasty/run/valueclasses-pavlov/C.class */ class C(x: scala.Predef.String) { def this() = { @@ -43,4 +43,4 @@ object Test { scala.Predef.println(f.box1(b1)) scala.Predef.println(f.box2(b2)) } -} +} \ No newline at end of file