From 8df8f4e401f29418afb3f047bbf64eb44238e88c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 18 Jun 2018 13:43:24 -0700 Subject: [PATCH 01/64] Check baseclasses when determining purity of class A class is pure for the purpose of reducing projections in inlinig if none of its baseclasses has an initializer. To make this robust wrt compilation order, we need to move computation of NoInits flags from Typer to the class completer. # Conflicts: # compiler/src/dotty/tools/dotc/typer/Inliner.scala --- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 22 +++++++++---------- .../dotty/tools/dotc/core/Definitions.scala | 5 +++-- .../tools/dotc/core/SymDenotations.scala | 6 +++++ .../src/dotty/tools/dotc/typer/Namer.scala | 2 ++ .../src/dotty/tools/dotc/typer/Typer.scala | 2 -- 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 69c415ff7e66..8ea555e5cc4b 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -240,6 +240,17 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] => case y => y } + /** The largest subset of {NoInits, PureInterface} that a + * trait enclosing this statement can have as flags. + */ + def defKind(tree: Tree): FlagSet = unsplice(tree) match { + case EmptyTree | _: Import => NoInitsInterface + case tree: TypeDef => if (tree.isClassDef) NoInits else NoInitsInterface + case tree: DefDef => if (tree.unforcedRhs == EmptyTree) NoInitsInterface else NoInits + case tree: ValDef => if (tree.unforcedRhs == EmptyTree) NoInitsInterface else EmptyFlags + case _ => EmptyFlags + } + /** Checks whether predicate `p` is true for all result parts of this expression, * where we zoom into Ifs, Matches, and Blocks. */ @@ -623,17 +634,6 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => accum(Nil, root) } - /** The largest subset of {NoInits, PureInterface} that a - * trait enclosing this statement can have as flags. - */ - def defKind(tree: Tree): FlagSet = unsplice(tree) match { - case EmptyTree | _: Import => NoInitsInterface - case tree: TypeDef => if (tree.isClassDef) NoInits else NoInitsInterface - case tree: DefDef => if (tree.unforcedRhs == EmptyTree) NoInitsInterface else NoInits - case tree: ValDef => if (tree.unforcedRhs == EmptyTree) NoInitsInterface else EmptyFlags - case _ => EmptyFlags - } - /** The top level classes in this tree, including only those module classes that * are not a linked class of some other class in the result. */ diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index f5cfb43745f8..b8635b90d85a 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -55,10 +55,10 @@ class Definitions { ctx.newSymbol(owner, name, flags | Permanent, info) private def newClassSymbol(owner: Symbol, name: TypeName, flags: FlagSet, infoFn: ClassSymbol => Type) = - ctx.newClassSymbol(owner, name, flags | Permanent, infoFn) + ctx.newClassSymbol(owner, name, flags | Permanent | NoInits, infoFn) private def enterCompleteClassSymbol(owner: Symbol, name: TypeName, flags: FlagSet, parents: List[TypeRef], decls: Scope = newScope) = - ctx.newCompleteClassSymbol(owner, name, flags | Permanent, parents, decls).entered + ctx.newCompleteClassSymbol(owner, name, flags | Permanent | NoInits, parents, decls).entered private def enterTypeField(cls: ClassSymbol, name: TypeName, flags: FlagSet, scope: MutableScope) = scope.enter(newSymbol(cls, name, flags, TypeBounds.empty)) @@ -275,6 +275,7 @@ class Definitions { val cls = ctx.requiredClass("java.lang.Object") assert(!cls.isCompleted, "race for completing java.lang.Object") cls.info = ClassInfo(cls.owner.thisType, cls, AnyClass.typeRef :: Nil, newScope) + cls.setFlag(NoInits) // The companion object doesn't really exist, `NoType` is the general // technique to do that. Here we need to set it before completing diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 95301176d78f..b5d308e43c46 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -599,6 +599,12 @@ object SymDenotations { final def isStable(implicit ctx: Context) = isType || !is(Erased) && (is(Stable) || !(is(UnstableValue) || info.isInstanceOf[ExprType])) + /** Is this a denotation of a class that does not have - either direct or inherited - + * initaliazion code? + */ + def isNoInitsClass(implicit ctx: Context) = + isClass && asClass.baseClasses.forall(_.is(NoInits)) + /** Is this a "real" method? A real method is a method which is: * - not an accessor * - not a label diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 342061d48471..38dda3b30a67 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -992,6 +992,8 @@ class Namer { typer: Typer => if (isDerivedValueClass(cls)) cls.setFlag(Final) cls.info = avoidPrivateLeaks(cls, cls.pos) cls.baseClasses.foreach(_.invalidateBaseTypeCache()) // we might have looked before and found nothing + cls.setNoInitsFlags((NoInitsInterface /: impl.body) ((fs, stat) => fs & untpd.defKind(stat))) + if (cls.isNoInitsClass) cls.primaryConstructor.setFlag(Stable) } } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index fa48b6315a63..cb7672832a76 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1534,8 +1534,6 @@ class Typer extends Namer val dummy = localDummy(cls, impl) val body1 = addAccessorDefs(cls, typedStats(impl.body, dummy)(ctx.inClassContext(self1.symbol))) - if (!ctx.isAfterTyper) - cls.setNoInitsFlags((NoInitsInterface /: body1) ((fs, stat) => fs & defKind(stat))) // Expand comments and type usecases if `-Ycook-comments` is set. if (ctx.settings.YcookComments.value) { From 8adf8db6b20e93dee43ea8d397c1958d03a36b85 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 18 Jun 2018 18:04:05 -0700 Subject: [PATCH 02/64] Better purity predictions Four improvments: 1. Applications of stable and pure functions to pure arguments are pure. This is important since constructors of pure classes are marked Stable. 2. New(...) expressions are pure (any side effects are caused by the constructor application). 3. Module values are pure of their module classes are pure. 4. scala.Product and scala.Serializable are assumed to be pure. Therefore, case classes can also be pure. These predictons remove most remaining unused bindings in the run/typelevel.scala test. --- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 25 +++++++++++-------- .../dotty/tools/dotc/core/Definitions.scala | 7 ++++++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 8ea555e5cc4b..88c53a122643 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -369,6 +369,8 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => refPurity(tree) case Select(qual, _) => refPurity(tree).min(exprPurity(qual)) + case New(_) => + SimplyPure case TypeApply(fn, _) => exprPurity(fn) /* @@ -380,13 +382,12 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => case Apply(fn, args) => def isKnownPureOp(sym: Symbol) = sym.owner.isPrimitiveValueClass || sym.owner == defn.StringClass - // Note: After uncurry, field accesses are represented as Apply(getter, Nil), - // so an Apply can also be pure. - if (args.isEmpty && fn.symbol.is(Stable)) exprPurity(fn) - else if (tree.tpe.isInstanceOf[ConstantType] && isKnownPureOp(tree.symbol)) - // A constant expression with pure arguments is pure. + if (tree.tpe.isInstanceOf[ConstantType] && isKnownPureOp(tree.symbol) + // A constant expression with pure arguments is pure. + || fn.symbol.isStable) minOf(exprPurity(fn), args.map(exprPurity)) `min` Pure - else Impure + else + Impure case Typed(expr, _) => exprPurity(expr) case Block(stats, expr) => @@ -413,11 +414,15 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => * @DarkDimius: need to make sure that lazy accessor methods have Lazy and Stable * flags set. */ - def refPurity(tree: Tree)(implicit ctx: Context): PurityLevel = - if (!tree.tpe.widen.isParameterless || tree.symbol.is(Erased)) SimplyPure - else if (!tree.symbol.isStable) Impure - else if (tree.symbol.is(Lazy)) Idempotent // TODO add Module flag, sinxce Module vals or not Lazy from the start. + def refPurity(tree: Tree)(implicit ctx: Context): PurityLevel = { + val sym = tree.symbol + if (!tree.tpe.widen.isParameterless || sym.is(Erased)) SimplyPure + else if (!sym.isStable) Impure + else if (sym.is(Module)) + if (sym.moduleClass.isNoInitsClass) Pure else Idempotent + else if (sym.is(Lazy)) Idempotent else SimplyPure + } def isPureRef(tree: Tree)(implicit ctx: Context) = refPurity(tree) == SimplyPure diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index b8635b90d85a..53642ac4b1c2 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1216,6 +1216,13 @@ class Definitions { for (m <- ScalaShadowingPackageClass.info.decls) ScalaPackageClass.enter(m) + // Temporary measure, as long as we do not read these classes from Tasty. + // Scala-2 classes don't have NoInits set even if they are pure. We override this + // for Product and Serializable so that case classes can be pure. A full solution + // requiers that we read all Scala code from Tasty. + ProductClass.setFlag(NoInits) + SerializableClass.setFlag(NoInits) + // force initialization of every symbol that is synthesized or hijacked by the compiler val forced = syntheticCoreClasses ++ syntheticCoreMethods ++ ScalaValueClasses() From 7d26a08cfbbdebe5693342dc83026d5fcb413dc9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 21 Jun 2018 12:15:33 -0400 Subject: [PATCH 03/64] Exclude self and super constructor calls from purity warnings Don't issue a "pure expression does nothing in statement position" for self and super constructor calls. They are conceptually unit-returning in this position anyway. --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 3 ++- tests/neg-custom-args/fatal-warnings/i2333.scala | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index cb7672832a76..4daac8b3bcb6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1917,7 +1917,8 @@ class Typer extends Namer traverse(stats ++ rest) case stat :: rest => val stat1 = typed(stat)(ctx.exprContext(stat, exprOwner)) - if (!ctx.isAfterTyper && isPureExpr(stat1) && !stat1.tpe.isRef(defn.UnitClass)) + if (!ctx.isAfterTyper && isPureExpr(stat1) && + !stat1.tpe.isRef(defn.UnitClass) && !isSelfOrSuperConstrCall(stat1)) ctx.warning(em"a pure expression does nothing in statement position", stat.pos) buf += stat1 traverse(rest) diff --git a/tests/neg-custom-args/fatal-warnings/i2333.scala b/tests/neg-custom-args/fatal-warnings/i2333.scala index a6772e77783e..a22433394346 100644 --- a/tests/neg-custom-args/fatal-warnings/i2333.scala +++ b/tests/neg-custom-args/fatal-warnings/i2333.scala @@ -1,4 +1,5 @@ @deprecated("bla", "2.11.0") class Foo { + println("") def this(x: Int) = this() } From eb49110b250cd3dfcd71da8a7bcf2be8bef2bcc4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 22 Jun 2018 21:10:47 -0400 Subject: [PATCH 04/64] Fix pure interface prediction for methods with default arguments Methods with default arguments in traits generate defender methods, so the trait cannot be a pure interface. Checking def kinds earlier in Namer rather than Typer exhibited that special case. --- compiler/src/dotty/tools/dotc/ast/TreeInfo.scala | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 88c53a122643..2d0b175f0aad 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -243,10 +243,13 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] => /** The largest subset of {NoInits, PureInterface} that a * trait enclosing this statement can have as flags. */ - def defKind(tree: Tree): FlagSet = unsplice(tree) match { + def defKind(tree: Tree)(implicit ctx: Context): FlagSet = unsplice(tree) match { case EmptyTree | _: Import => NoInitsInterface case tree: TypeDef => if (tree.isClassDef) NoInits else NoInitsInterface - case tree: DefDef => if (tree.unforcedRhs == EmptyTree) NoInitsInterface else NoInits + case tree: DefDef => + if (tree.unforcedRhs == EmptyTree && + tree.vparamss.forall(_.forall(_.rhs.isEmpty))) NoInitsInterface + else NoInits case tree: ValDef => if (tree.unforcedRhs == EmptyTree) NoInitsInterface else EmptyFlags case _ => EmptyFlags } @@ -416,7 +419,8 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => */ def refPurity(tree: Tree)(implicit ctx: Context): PurityLevel = { val sym = tree.symbol - if (!tree.tpe.widen.isParameterless || sym.is(Erased)) SimplyPure + if (!tree.hasType) Impure + else if (!tree.tpe.widen.isParameterless || sym.is(Erased)) SimplyPure else if (!sym.isStable) Impure else if (sym.is(Module)) if (sym.moduleClass.isNoInitsClass) Pure else Idempotent From 603974edd77ad1853dc9a424be9b5d63c65026e9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 22 Jun 2018 21:12:26 -0400 Subject: [PATCH 05/64] Account for stable constrictors in ExtractAPI Constructors (and potentially, in the future, other methods) can have the stable flag set. We need to adapt ExtractAPI to this change. --- compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index e26fe556510b..4dcd282852c3 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -328,7 +328,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder } else if (sym.is(Mutable, butNot = Accessor)) { api.Var.of(sym.name.toString, apiAccess(sym), apiModifiers(sym), apiAnnotations(sym).toArray, apiType(sym.info)) - } else if (sym.isStable) { + } else if (sym.isStable && !sym.isRealMethod) { api.Val.of(sym.name.toString, apiAccess(sym), apiModifiers(sym), apiAnnotations(sym).toArray, apiType(sym.info)) } else { From 49a28de7e5c472a4977b56409bcd9f09f73bbba4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 26 May 2018 17:06:59 +0200 Subject: [PATCH 06/64] Introduce UntypedSplice Introduce UntypedSplice and make TreeCopiers and TreeMaps more regular to take splices into account. --- .../dotty/tools/dotc/ast/TreeTypeMap.scala | 4 ++-- compiler/src/dotty/tools/dotc/ast/Trees.scala | 7 +++++-- compiler/src/dotty/tools/dotc/ast/tpd.scala | 21 ++++++++++++++++++- compiler/src/dotty/tools/dotc/ast/untpd.scala | 18 ++++++++++------ .../tools/dotc/printing/RefinedPrinter.scala | 3 +++ .../tools/dotc/transform/MacroTransform.scala | 2 ++ .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- 7 files changed, 45 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala index e52213cf0485..2bc7ac6b2b13 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 @@ -38,7 +38,7 @@ class TreeTypeMap( val oldOwners: List[Symbol] = Nil, val newOwners: List[Symbol] = Nil, val substFrom: List[Symbol] = Nil, - val substTo: List[Symbol] = Nil)(implicit ctx: Context) extends tpd.TreeMap { + val substTo: List[Symbol] = Nil)(implicit ctx: Context) extends tpd.TypedTreeMap { import tpd._ /** If `sym` is one of `oldOwners`, replace by corresponding symbol in `newOwners` */ diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 9f72feb216b9..74dc07e8fe99 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -1148,6 +1148,9 @@ object Trees { abstract class TreeMap(val cpy: TreeCopier = inst.cpy) { + protected def handleMoreCases(tree: Tree)(implicit ctx: Context): Tree = + if (ctx.reporter.errorsReported) tree else throw new MatchError(tree) + def transform(tree: Tree)(implicit ctx: Context): Tree = { Stats.record(s"TreeMap.transform $getClass") Stats.record("TreeMap.transform total") @@ -1245,8 +1248,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 _ => + handleMoreCases(tree) } } diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index a9feeae2da99..12f78c02b903 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -324,7 +324,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 @@ -471,6 +471,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } else foldOver(sym, tree) } + case class UntypedSplice(splice: untpd.Tree) extends Tree + override val cpy: TypedTreeCopier = // Type ascription needed to pick up any new members in TreeCopier (currently there are none) new TypedTreeCopier @@ -609,6 +611,11 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } } + 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)) + } + override def If(tree: If)(cond: Tree = tree.cond, thenp: Tree = tree.thenp, elsep: Tree = tree.elsep)(implicit ctx: Context): If = If(tree: Tree)(cond, thenp, elsep) override def Closure(tree: Closure)(env: List[Tree] = tree.env, meth: Tree = tree.meth, tpt: Tree = tree.tpt)(implicit ctx: Context): Closure = @@ -638,6 +645,18 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { Closure(tree: Tree)(env, meth, tpt) } + class TypedTreeMap(cpy: TypedTreeCopier = tpd.cpy) extends TreeMap(cpy) { self => + override def handleMoreCases(tree: Tree)(implicit ctx: Context) = tree match { + case UntypedSplice(utree) => + val umap = new untpd.UntypedTreeMap() { + override def typedMap = self.transform(_) + } + cpy.UntypedSplice(tree)(umap.transform(utree)) + case _ => + super.handleMoreCases(tree) + } + } + override def skipTransform(tree: Tree)(implicit ctx: Context) = tree.tpe.isError implicit class TreeOps[ThisTree <: tpd.Tree](val tree: ThisTree) extends AnyVal { diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 80e4365a943c..1629210d1e03 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -24,8 +24,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { /** A typed subtree of an untyped tree needs to be wrapped in a TypedSlice * @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 { @@ -496,10 +496,16 @@ 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 { + protected def typedMap: tpd.Tree => tpd.Tree = identity + + override def handleMoreCases(tree: Tree)(implicit ctx: Context): Tree = tree match { case ModuleDef(name, impl) => cpy.ModuleDef(tree)(name, transformSub(impl)) case ParsedTry(expr, handler, finalizer) => @@ -540,10 +546,10 @@ 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 TypedSplice(_) => - tree + case TypedSplice(splice) => + cpy.TypedSplice(tree)(typedMap(splice)) case _ => - super.transform(tree) + super.handleMoreCases(tree) } } diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index f1cf87ad9c1f..e957188129ca 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -446,6 +446,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) ~ "]#US#" + 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/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/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4daac8b3bcb6..9212727870b1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1663,7 +1663,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 => From 3bc3e3b2f5dfbad2ac3b96ea54998dc57e590949 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 26 May 2018 19:48:52 +0200 Subject: [PATCH 07/64] More robust scheme for untyped splices - extend scheme to accumulators - no need anymore to define special tpd versions of maps and accumulators - no extra `typeMap` field needed in typeMap; instead the UntypedSplice case is handled directly. --- .../dotty/tools/dotc/ast/TreeTypeMap.scala | 2 +- compiler/src/dotty/tools/dotc/ast/Trees.scala | 57 +++++++++++++++---- compiler/src/dotty/tools/dotc/ast/tpd.scala | 17 ------ compiler/src/dotty/tools/dotc/ast/untpd.scala | 24 ++++---- 4 files changed, 59 insertions(+), 41 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala index 2bc7ac6b2b13..ccadcf15d4da 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -38,7 +38,7 @@ class TreeTypeMap( val oldOwners: List[Symbol] = Nil, val newOwners: List[Symbol] = Nil, val substFrom: List[Symbol] = Nil, - val substTo: List[Symbol] = Nil)(implicit ctx: Context) extends tpd.TypedTreeMap { + val substTo: List[Symbol] = Nil)(implicit ctx: Context) extends tpd.TreeMap { import tpd._ /** If `sym` is one of `oldOwners`, replace by corresponding symbol in `newOwners` */ diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 74dc07e8fe99..a100c14cd57c 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -1109,6 +1109,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,10 +1150,7 @@ object Trees { */ protected def inlineContext(call: Tree)(implicit ctx: Context): Context = ctx - abstract class TreeMap(val cpy: TreeCopier = inst.cpy) { - - protected def handleMoreCases(tree: Tree)(implicit ctx: Context): Tree = - if (ctx.reporter.errorsReported) tree else throw new MatchError(tree) + abstract class TreeMap(val cpy: TreeCopier = inst.cpy) { self => def transform(tree: Tree)(implicit ctx: Context): Tree = { Stats.record(s"TreeMap.transform $getClass") @@ -1249,7 +1250,7 @@ object Trees { val trees1 = transform(trees) if (trees1 eq trees) tree else Thicket(trees1) case _ => - handleMoreCases(tree) + transformMoreCases(tree) } } @@ -1261,9 +1262,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 @@ -1358,14 +1376,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 12f78c02b903..a0c76fd90947 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -611,11 +611,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } } - 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)) - } - override def If(tree: If)(cond: Tree = tree.cond, thenp: Tree = tree.thenp, elsep: Tree = tree.elsep)(implicit ctx: Context): If = If(tree: Tree)(cond, thenp, elsep) override def Closure(tree: Closure)(env: List[Tree] = tree.env, meth: Tree = tree.meth, tpt: Tree = tree.tpt)(implicit ctx: Context): Closure = @@ -645,18 +640,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { Closure(tree: Tree)(env, meth, tpt) } - class TypedTreeMap(cpy: TypedTreeCopier = tpd.cpy) extends TreeMap(cpy) { self => - override def handleMoreCases(tree: Tree)(implicit ctx: Context) = tree match { - case UntypedSplice(utree) => - val umap = new untpd.UntypedTreeMap() { - override def typedMap = self.transform(_) - } - cpy.UntypedSplice(tree)(umap.transform(utree)) - case _ => - super.handleMoreCases(tree) - } - } - override def skipTransform(tree: Tree)(implicit ctx: Context) = tree.tpe.isError implicit class TreeOps[ThisTree <: tpd.Tree](val tree: ThisTree) extends AnyVal { diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 1629210d1e03..74734a158109 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -503,9 +503,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { } abstract class UntypedTreeMap(cpy: UntypedTreeCopier = untpd.cpy) extends TreeMap(cpy) { - protected def typedMap: tpd.Tree => tpd.Tree = identity - - override def handleMoreCases(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) => @@ -546,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 TypedSplice(splice) => - cpy.TypedSplice(tree)(typedMap(splice)) + case tpd.UntypedSplice(splice) => + cpy.UntypedSplice(tree)(transform(splice)) + case TypedSplice(_) => + tree case _ => - super.handleMoreCases(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) => @@ -595,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) } } From 52ac21a6a268dc7839fd8196a0992ebf7f9cc2e7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 28 May 2018 11:28:33 +0200 Subject: [PATCH 08/64] Make TreePickler untpd friendly Some refactorings so that it will become easier to generate untyped trees --- .../tools/dotc/core/tasty/TreePickler.scala | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index c2b22ade4ed6..04cc3b12af34 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -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) } @@ -577,15 +577,24 @@ 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) @@ -598,7 +607,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 +617,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 +626,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 { From 444ce466958876edbf1bcdde7c65570cc32df3cf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 29 May 2018 15:37:13 +0200 Subject: [PATCH 09/64] More debug options for printing - Mark splices in code under -print-debug - Print owners of all definitions under -print-debug-owners --- compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index e957188129ca..3d9c9bc13bca 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -447,8 +447,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { if (ctx.settings.YprintDebug.value) "[" ~ toText(t) ~ "]#TS#" else toText(t) case tpd.UntypedSplice(t) => - if (ctx.settings.YprintDebug.value) "[" ~ toText(t) ~ "]#US#" - toText(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) From 1f33e9d9a336640c342cafd61cf73fa19276f07a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 29 May 2018 16:37:52 +0200 Subject: [PATCH 10/64] Add untyped types to Tasty Allow UNTYPEDSPLICE roots with TYPEDSPLICE subtrees. Trees between them are untyped. --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 2 +- .../tools/dotc/core/tasty/TastyFormat.scala | 25 +- .../tools/dotc/core/tasty/TreePickler.scala | 211 +++++++++++++++- .../tools/dotc/core/tasty/TreeUnpickler.scala | 235 ++++++++++++++++-- 4 files changed, 448 insertions(+), 25 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index a0c76fd90947..dec1e47f258c 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -642,7 +642,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 diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index f1237189459c..14d7a5544fcb 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 Mods 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 @@ -203,6 +205,13 @@ Standard-Section: "ASTs" TopLevelStat* Annotation = ANNOTATION Length tycon_Type fullAnnotation_Term +// --------------- untyped additions ------------------------------------------ + + TermUntyped = Term + FUNCTION Length body_Term arg_Term* + INFIXOP Length op_NameRef left_Term right_Term + TYPEDSPLICE Length splice_Term + 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 @@ -408,6 +417,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 +427,13 @@ 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 + def methodType(isImplicit: Boolean = false, isErased: Boolean = false) = { val implicitOffset = if (isImplicit) 1 else 0 val erasedOffset = if (isErased) 2 else 0 @@ -560,6 +577,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 +645,18 @@ object TastyFormat { case PRIVATEqualified => "PRIVATEqualified" case PROTECTEDqualified => "PROTECTEDqualified" case HOLE => "HOLE" + + case UNTYPEDSPLICE => "UNTYPEDSPLICE" + case TYPEDSPLICE => "TYPEDSPLICE" + case FUNCTION => "FUNCTION" + case INFIXOP => "INFIXOP" } /** @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/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 04cc3b12af34..7d25d0889874 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 @@ -546,6 +546,10 @@ class TreePickler(pickler: TastyPickler) { pickleTree(lo); if (hi ne lo) pickleTree(hi) } + case tpd.UntypedSplice(splice) => + //println(i"UNTYPED: $splice") + writeByte(UNTYPEDSPLICE) + withLength { pickleUntyped(splice); pickleType(tree.tpe) } case Hole(idx, args) => writeByte(HOLE) withLength { @@ -644,6 +648,211 @@ class TreePickler(pickler: TastyPickler) { withLength { pickleType(ann.symbol.typeRef); pickleTree(ann.tree) } } +// ---- pickling untyped trees ---------------------------------- + + def pickleUntyped(tree: untpd.Tree)(implicit ctx: Context): Unit = { + try desugar(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) + pickleUntyped(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(pickleUntyped) + } + case Literal(const) => + pickleConstant(const) + case Super(qual, mix) => + writeByte(SUPER) + withLength { + pickleUntyped(qual); + if (!mix.isEmpty) pickleUntyped(mix) + } + case New(tpt) => + writeByte(NEW) + pickleUntyped(tpt) + case Typed(expr, tpt) => + writeByte(TYPED) + withLength { pickleUntyped(expr); pickleUntyped(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); pickleUntypedUnlessEmpty(guard) } + case Return(expr, from) => + writeByte(RETURN) + withLength { pickleDummyRef(); pickleUntypedUnlessEmpty(expr) } + case Try(block, cases, finalizer) => + writeByte(TRY) + withLength { pickleUntyped(block); cases.foreach(pickleUntyped); pickleUntypedUnlessEmpty(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 => + pickleUntypedDef(VALDEF, tree, tree.tpt, tree.rhs) + case tree: untpd.DefDef => + pickleUntypedDef(DEFDEF, tree, tree.tpt, tree.rhs, pickleAllUntypedParams(tree)) + case tree: untpd.TypeDef => + pickleUntypedDef(TYPEDEF, tree, tree.rhs) + case tree: untpd.ModuleDef => + pickleUntypedDef(OBJECTDEF, tree, tree.impl) + case tree: untpd.Template => + writeByte(TEMPLATE) + tree.parents.foreach(pickleUntyped) + if (!tree.self.isEmpty) { + writeByte(SELFDEF); pickleName(tree.self.name); pickleUntyped(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) + pickleUntyped(ref) + case RefinedTypeTree(parent, refinements) => + writeByte(REFINEDtpt) + withLength { pickleUntyped(parent); refinements.foreach(pickleUntyped) } + case AppliedTypeTree(tycon, args) => + writeByte(APPLIEDtpt) + withLength { pickleUntyped(tycon); args.foreach(pickleUntyped) } + case AndTypeTree(tp1, tp2) => + writeByte(ANDtpt) + withLength { pickleUntyped(tp1); pickleUntyped(tp2) } + case OrTypeTree(tp1, tp2) => + writeByte(ORtpt) + withLength { pickleUntyped(tp1); pickleUntyped(tp2) } + case ByNameTypeTree(tp) => + writeByte(BYNAMEtpt) + pickleUntyped(tp) + case Annotated(tree, annot) => + writeByte(ANNOTATEDtpt) + withLength { pickleUntyped(tree); pickleUntyped(annot) } + case LambdaTypeTree(tparams, body) => + writeByte(LAMBDAtpt) + withLength { pickleUntypedParams(tparams); pickleUntyped(body) } + case TypeBoundsTree(lo, hi) => + writeByte(TYPEBOUNDStpt) + withLength { + pickleUntyped(lo); + if (hi ne lo) pickleUntyped(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 Thicket(trees) => + trees.foreach(pickleUntyped) + case untpd.TypedSplice(splice) => + writeByte(TYPEDSPLICE) + withLength { pickleTree(splice) } + } + catch { + case ex: AssertionError => + println(i"error when pickling tree $tree") + throw ex + } + } + + def pickleUntypedUnlessEmpty(tree: untpd.Tree)(implicit ctx: Context): Unit = + if (!tree.isEmpty) pickleUntyped(tree) + + def pickleAllUntypedParams(tree: untpd.DefDef)(implicit ctx: Context): Unit = { + pickleUntypedParams(tree.tparams) + for (vparams <- tree.vparamss) { + writeByte(PARAMS) + withLength { pickleUntypedParams(vparams) } + } + } + + def pickleUntypedParams(trees: List[untpd.Tree])(implicit ctx: Context): Unit = + trees.foreach(pickleUntypedParam) + + def pickleUntypedDef(tag: Int, tree: untpd.MemberDef, tpt: untpd.Tree, rhs: untpd.Tree = untpd.EmptyTree, pickleParams: => Unit = ())(implicit ctx: Context) = { + import untpd.modsDeco + writeByte(tag) + withLength { + pickleName(tree.name) + pickleParams + pickleUntyped(tpt) + pickleUntypedUnlessEmpty(rhs) + pickleUntypedModifiers(tree.mods) + } + } + + def pickleUntypedParam(tree: untpd.Tree)(implicit ctx: Context): Unit = tree match { + case tree: untpd.ValDef => pickleUntypedDef(PARAM, tree, tree.tpt) + case tree: untpd.DefDef => pickleUntypedDef(PARAM, tree, tree.tpt, tree.rhs) + case tree: untpd.TypeDef => pickleUntypedDef(TYPEPARAM, tree, tree.rhs) + } + + def pickleUntypedModifiers(mods: untpd.Modifiers)(implicit ctx: Context): 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 + } + mods.annotations.foreach(pickleUntypedAnnotation) + } + + def pickleUntypedAnnotation(annotTree: untpd.Tree)(implicit ctx: Context) = { + writeByte(ANNOTATION) + withLength { pickleDummyType(); pickleUntyped(annotTree) } + } + + def pickleDummyRef(): Unit = writeNat(0) + + def pickleDummyType(): Unit = { + writeByte(SHAREDtype) + pickleDummyRef() + } + +// ---- 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 74378066b9b7..bf4b19636a88 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -535,7 +535,7 @@ class TreeUnpickler(reader: TastyReader, rootd.symbol case _ => val completer = adjustIfModule(new Completer(ctx.owner, subReader(start, end))) - if (isClass) +F if (isClass) ctx.newClassSymbol(ctx.owner, name.asTypeName, flags, completer, privateWithin, coord) else ctx.newSymbol(ctx.owner, name, flags, completer, privateWithin, coord) @@ -563,10 +563,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 => AnnotType, readWithin: Context => WithinType, defaultWithin: WithinType) + (implicit ctx: Context): (FlagSet, List[AnnotType], WithinType) = { var flags: FlagSet = EmptyFlags var annotFns: List[Symbol => Annotation] = Nil - var privateWithin: Symbol = NoSymbol + var privateWithin = defaultWithin while (currentAddr.index != end.index) { def addFlag(flag: FlagSet) = { flags |= flag @@ -612,10 +614,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 => @@ -625,6 +627,9 @@ class TreeUnpickler(reader: TastyReader, (flags, annotFns.reverse, privateWithin) } + private val readWithin: Context => Symbol = + implicit ctx => readType().typeSymbol + private val readAnnot: Context => Symbol => Annotation = { implicit ctx => readByte() @@ -924,26 +929,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)) @@ -1131,6 +1137,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 +1208,189 @@ 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() == SHAREDtype) + assert(readNat() == 0) + } + + def readIdent(): untpd.Ident = readUntyped().asInstanceOf[untpd.Ident] + + def readParams[T <: untpd.MemberDef](tag: Int): List[T] = + collectWhile(nextByte == tag)(readUntyped().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 SHAREDtype => + assert(readByte() == 0) + 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) + } + + def readRhs(): untpd.Tree = + if (noRhs(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)(lo, readUntyped()) + untpd.TypeBoundsTree(lo, hi) + case TYPEDSPLICE => + untpd.TypedSplice(readTerm()) + case FUNCTION => + val body = readUntyped() + val params = until(end)(readUntyped()) + untpd.Function(params, body) + case INFIXOP => + untpd.InfixOp(readUntyped(), readIdent(), readUntyped()) + } + 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 => untpd.Tree = + implicit ctx => readUntyped() // ------ Setting positions ------------------------------------------------ From 2a2cb913848b31b4c437aa4ccdf486b778f21075 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 29 May 2018 18:14:33 +0200 Subject: [PATCH 11/64] Tweaks to pickling and unpickling untyped trees - need to maintain Type mode bit for correct desugaring - need to pickle PatDefs directly since desugaring requires too much context. --- .../tools/dotc/core/tasty/TastyFormat.scala | 7 +- .../tools/dotc/core/tasty/TreePickler.scala | 392 +++++++++--------- .../tools/dotc/core/tasty/TreeUnpickler.scala | 9 +- 3 files changed, 217 insertions(+), 191 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index 14d7a5544fcb..ca3ab6e14e21 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -59,7 +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 Mods + OBJECTDEF Length NameRef Template Modifier* IMPORT Length qual_Term Selector* Selector = IMPORTED name_NameRef RENAMED to_NameRef @@ -208,9 +208,10 @@ Standard-Section: "ASTs" TopLevelStat* // --------------- untyped additions ------------------------------------------ TermUntyped = Term + TYPEDSPLICE Length splice_Term FUNCTION Length body_Term arg_Term* INFIXOP Length op_NameRef left_Term right_Term - TYPEDSPLICE Length splice_Term + PATDEF Length type_Term rhs_Term pattern_Term* Modifier* 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. @@ -433,6 +434,7 @@ object TastyFormat { 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 @@ -650,6 +652,7 @@ object TastyFormat { 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. diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 7d25d0889874..cce9dcb1f6ae 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -547,7 +547,6 @@ class TreePickler(pickler: TastyPickler) { if (hi ne lo) pickleTree(hi) } case tpd.UntypedSplice(splice) => - //println(i"UNTYPED: $splice") writeByte(UNTYPEDSPLICE) withLength { pickleUntyped(splice); pickleType(tree.tpe) } case Hole(idx, args) => @@ -651,204 +650,223 @@ class TreePickler(pickler: TastyPickler) { // ---- pickling untyped trees ---------------------------------- def pickleUntyped(tree: untpd.Tree)(implicit ctx: Context): Unit = { - try desugar(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) - pickleUntyped(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(pickleUntyped) - } - case Literal(const) => - pickleConstant(const) - case Super(qual, mix) => - writeByte(SUPER) - withLength { - pickleUntyped(qual); - if (!mix.isEmpty) pickleUntyped(mix) - } - case New(tpt) => - writeByte(NEW) - pickleUntyped(tpt) - case Typed(expr, tpt) => - writeByte(TYPED) - withLength { pickleUntyped(expr); pickleUntyped(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); pickleUntypedUnlessEmpty(guard) } - case Return(expr, from) => - writeByte(RETURN) - withLength { pickleDummyRef(); pickleUntypedUnlessEmpty(expr) } - case Try(block, cases, finalizer) => - writeByte(TRY) - withLength { pickleUntyped(block); cases.foreach(pickleUntyped); pickleUntypedUnlessEmpty(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 => - pickleUntypedDef(VALDEF, tree, tree.tpt, tree.rhs) - case tree: untpd.DefDef => - pickleUntypedDef(DEFDEF, tree, tree.tpt, tree.rhs, pickleAllUntypedParams(tree)) - case tree: untpd.TypeDef => - pickleUntypedDef(TYPEDEF, tree, tree.rhs) - case tree: untpd.ModuleDef => - pickleUntypedDef(OBJECTDEF, tree, tree.impl) - case tree: untpd.Template => - writeByte(TEMPLATE) - tree.parents.foreach(pickleUntyped) - if (!tree.self.isEmpty) { - writeByte(SELFDEF); pickleName(tree.self.name); pickleUntyped(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) - pickleUntyped(ref) - case RefinedTypeTree(parent, refinements) => - writeByte(REFINEDtpt) - withLength { pickleUntyped(parent); refinements.foreach(pickleUntyped) } - case AppliedTypeTree(tycon, args) => - writeByte(APPLIEDtpt) - withLength { pickleUntyped(tycon); args.foreach(pickleUntyped) } - case AndTypeTree(tp1, tp2) => - writeByte(ANDtpt) - withLength { pickleUntyped(tp1); pickleUntyped(tp2) } - case OrTypeTree(tp1, tp2) => - writeByte(ORtpt) - withLength { pickleUntyped(tp1); pickleUntyped(tp2) } - case ByNameTypeTree(tp) => - writeByte(BYNAMEtpt) - pickleUntyped(tp) - case Annotated(tree, annot) => - writeByte(ANNOTATEDtpt) - withLength { pickleUntyped(tree); pickleUntyped(annot) } - case LambdaTypeTree(tparams, body) => - writeByte(LAMBDAtpt) - withLength { pickleUntypedParams(tparams); pickleUntyped(body) } - case TypeBoundsTree(lo, hi) => - writeByte(TYPEBOUNDStpt) - withLength { - pickleUntyped(lo); - if (hi ne lo) pickleUntyped(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 Thicket(trees) => - trees.foreach(pickleUntyped) - case untpd.TypedSplice(splice) => - writeByte(TYPEDSPLICE) - withLength { pickleTree(splice) } - } - catch { - case ex: AssertionError => - println(i"error when pickling tree $tree") - throw ex - } - } - def pickleUntypedUnlessEmpty(tree: untpd.Tree)(implicit ctx: Context): Unit = - if (!tree.isEmpty) pickleUntyped(tree) + def pickleDummyRef(): Unit = writeNat(0) - def pickleAllUntypedParams(tree: untpd.DefDef)(implicit ctx: Context): Unit = { - pickleUntypedParams(tree.tparams) - for (vparams <- tree.vparamss) { - writeByte(PARAMS) - withLength { pickleUntypedParams(vparams) } + def pickleDummyType(): Unit = { + writeByte(SHAREDtype) + pickleDummyRef() } - } - def pickleUntypedParams(trees: List[untpd.Tree])(implicit ctx: Context): Unit = - trees.foreach(pickleUntypedParam) + def pickleUnlessEmpty(tree: untpd.Tree): Unit = + if (!tree.isEmpty) pickleUntyped(tree) - def pickleUntypedDef(tag: Int, tree: untpd.MemberDef, tpt: untpd.Tree, rhs: untpd.Tree = untpd.EmptyTree, pickleParams: => Unit = ())(implicit ctx: Context) = { - import untpd.modsDeco - writeByte(tag) - withLength { - pickleName(tree.name) - pickleParams - pickleUntyped(tpt) - pickleUntypedUnlessEmpty(rhs) - pickleUntypedModifiers(tree.mods) + 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 pickleUntypedParam(tree: untpd.Tree)(implicit ctx: Context): Unit = tree match { - case tree: untpd.ValDef => pickleUntypedDef(PARAM, tree, tree.tpt) - case tree: untpd.DefDef => pickleUntypedDef(PARAM, tree, tree.tpt, tree.rhs) - case tree: untpd.TypeDef => pickleUntypedDef(TYPEPARAM, tree, tree.rhs) - } + def pickleParams(trees: List[untpd.Tree]): Unit = + trees.foreach(pickleParam) - def pickleUntypedModifiers(mods: untpd.Modifiers)(implicit ctx: Context): 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 + 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) } - mods.annotations.foreach(pickleUntypedAnnotation) - } - def pickleUntypedAnnotation(annotTree: untpd.Tree)(implicit ctx: Context) = { - writeByte(ANNOTATION) - withLength { pickleDummyType(); pickleUntyped(annotTree) } - } + 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) + } + } - def pickleDummyRef(): Unit = writeNat(0) + def pickleModifiers(mods: untpd.Modifiers): 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 + } + mods.annotations.foreach(pickleAnnotation) + } - def pickleDummyType(): Unit = { - writeByte(SHAREDtype) - pickleDummyRef() + 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) + 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) + } + case untpd.TypedSplice(splice) => + writeByte(TYPEDSPLICE) + withLength { pickleTree(splice) } + case Thicket(trees) => + trees.foreach(pickleUntyped) + case _ => + pickleUntyped(desugar(tree)) + } + catch { + case ex: AssertionError => + println(i"error when pickling tree $tree") + throw ex + } } // ---- main entry points --------------------------------------- diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index bf4b19636a88..1833b60ee8cc 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1267,7 +1267,7 @@ F if (isClass) case NAMEDARG => untpd.NamedArg(readName(), readUntyped()) case SHAREDtype => - assert(readByte() == 0) + assert(readNat() == 0) untpd.TypeTree() case _ => untpd.Literal(readConstant(tag)) @@ -1283,7 +1283,7 @@ F if (isClass) } def readRhs(): untpd.Tree = - if (noRhs(end)) untpd.EmptyTree else readUntyped() + if (nothingButMods(end)) untpd.EmptyTree else readUntyped() val result = (tag: @switch) match { case SUPER => @@ -1377,6 +1377,11 @@ F if (isClass) 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 From 798fbf73421d52f2c84f18836b04e196440047bf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 30 May 2018 12:11:57 +0200 Subject: [PATCH 12/64] Fix pickler printing of TERMREFin, TYPEREFin --- compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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() From fc60ae9bd6db584143ba1a4c5dc20b0f31a39b3b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 30 May 2018 12:12:54 +0200 Subject: [PATCH 13/64] Fix pickling of flags in untyped mode --- compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index cce9dcb1f6ae..0ed6b2955a6a 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -694,11 +694,11 @@ class TreePickler(pickler: TastyPickler) { pickleParams pickleTpt(tpt) pickleUnlessEmpty(rhs) - pickleModifiers(tree.mods) + pickleModifiers(tree.mods, tree.name.isTermName) } } - def pickleModifiers(mods: untpd.Modifiers): Unit = { + def pickleModifiers(mods: untpd.Modifiers, isTerm: Boolean): Unit = { import Flags._ var flags = mods.flags val privateWithin = mods.privateWithin @@ -707,6 +707,7 @@ class TreePickler(pickler: TastyPickler) { pickleUntyped(untpd.Ident(privateWithin)) flags = flags &~ Protected } + pickleFlags(flags, isTerm) mods.annotations.foreach(pickleAnnotation) } @@ -852,7 +853,7 @@ class TreePickler(pickler: TastyPickler) { pickleTpt(tpt) pickleUntyped(rhs) pats.foreach(pickleUntyped) - pickleModifiers(mods) + pickleModifiers(mods, isTerm = true) } case untpd.TypedSplice(splice) => writeByte(TYPEDSPLICE) From 1a257bac425420af774441799d935bafeb709e54 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 3 Jun 2018 16:16:12 +0200 Subject: [PATCH 14/64] Add transparent modifier --- compiler/src/dotty/tools/dotc/core/Mode.scala | 6 ++++-- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 2 +- .../src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 4 ++-- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Namer.scala | 3 ++- compiler/src/dotty/tools/dotc/typer/Typer.scala | 1 + 6 files changed, 11 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index 020b884b22b3..b7f47122d827 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -94,7 +94,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/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index b5d308e43c46..024356574a71 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -789,7 +789,7 @@ object SymDenotations { 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 diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 1833b60ee8cc..f4dcc136ad5c 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -535,7 +535,7 @@ class TreeUnpickler(reader: TastyReader, rootd.symbol case _ => val completer = adjustIfModule(new Completer(ctx.owner, subReader(start, end))) -F if (isClass) + if (isClass) ctx.newClassSymbol(ctx.owner, name.asTypeName, flags, completer, privateWithin, coord) else ctx.newSymbol(ctx.owner, name, flags, completer, privateWithin, coord) @@ -550,7 +550,7 @@ F if (isClass) sym.completer.withDecls(newScope) forkAt(templateStart).indexTemplateParams()(localContext(sym)) } - else if (sym.isInlineableMethod) + else if (sym.isInlinedMethod) sym.addAnnotation(LazyBodyAnnotation { ctx0 => implicit val ctx: Context = localContext(sym)(ctx0).addMode(Mode.ReadPositions) // avoids space leaks by not capturing the current context diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index ca87c1f58c42..83a351b2b954 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -232,7 +232,7 @@ object Inliner { * from Scala2x class files might be `@inline`, 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.isInlineableMethod && sym.hasAnnotation(defn.BodyAnnot) /** The body to inline for method `sym`. * @pre hasBodyToInline(sym) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 38dda3b30a67..1463a72ca8ab 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1134,7 +1134,8 @@ 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) def rhsType = typedAheadExpr(mdef.rhs, inherited orElse rhsProto)(rhsCtx).tpe // Approximate a type `tp` with a type that does not contain skolem types. diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 9212727870b1..354c5ba99166 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1428,6 +1428,7 @@ class Typer extends Namer (tparams1, sym.owner.typeParams).zipped.foreach ((tdef, tparam) => rhsCtx.gadt.setBounds(tdef.symbol, TypeAlias(tparam.typeRef))) } + if (sym.isTransparentMethod) rhsCtx = rhsCtx.addMode(Mode.TransparentBody) val rhs1 = normalizeErasedRhs(typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx), sym) // Overwrite inline body to make sure it is not evaluated twice From e95a000ab12cc19e9fd5ee96ffb3c988e4320918 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 3 Jun 2018 16:55:36 +0200 Subject: [PATCH 15/64] Fixes to pickling and unpickling untyped trees Among others, adds new tag for EMPTYTREE. The serialized info is ambiguous otherwise. E.g. TypeBounds(Empty, B) and TypeBounds(B, Empty) serialize in the same way. --- .../tools/dotc/core/tasty/TastyFormat.scala | 4 +++- .../tools/dotc/core/tasty/TreePickler.scala | 3 ++- .../tools/dotc/core/tasty/TreeUnpickler.scala | 17 ++++++++++++++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index ca3ab6e14e21..de1e04fcadf2 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -317,6 +317,7 @@ object TastyFormat { final val MACRO = 34 final val ERASED = 35 final val PARAMsetter = 36 + final val EMPTYTREE = 37 // Cat. 2: tag Nat @@ -451,7 +452,7 @@ object TastyFormat { /** Useful for debugging */ def isLegalTag(tag: Int) = - firstSimpleTreeTag <= tag && tag <= PARAMsetter || + firstSimpleTreeTag <= tag && tag <= EMPTYTREE || firstNatTreeTag <= tag && tag <= SYMBOLconst || firstASTTreeTag <= tag && tag <= SINGLETONtpt || firstNatASTTreeTag <= tag && tag <= NAMEDARG || @@ -548,6 +549,7 @@ object TastyFormat { case DEFAULTparameterized => "DEFAULTparameterized" case STABLE => "STABLE" case PARAMsetter => "PARAMsetter" + case EMPTYTREE => "EMPTYTREE" case SHAREDterm => "SHAREDterm" case SHAREDtype => "SHAREDtype" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 0ed6b2955a6a..4175aa41f3b0 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -859,7 +859,8 @@ class TreePickler(pickler: TastyPickler) { writeByte(TYPEDSPLICE) withLength { pickleTree(splice) } case Thicket(trees) => - trees.foreach(pickleUntyped) + if (trees.isEmpty) writeByte(EMPTYTREE) + else trees.foreach(pickleUntyped) case _ => pickleUntyped(desugar(tree)) } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index f4dcc136ad5c..9e5642f14b3d 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1223,7 +1223,11 @@ class TreeUnpickler(reader: TastyReader, def readIdent(): untpd.Ident = readUntyped().asInstanceOf[untpd.Ident] def readParams[T <: untpd.MemberDef](tag: Int): List[T] = - collectWhile(nextByte == tag)(readUntyped().asInstanceOf[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) { @@ -1266,8 +1270,11 @@ class TreeUnpickler(reader: TastyReader, untpd.ByNameTypeTree(readUntyped()) case NAMEDARG => untpd.NamedArg(readName(), readUntyped()) + case EMPTYTREE => + untpd.EmptyTree case SHAREDtype => - assert(readNat() == 0) + val b = readNat() + assert(b == 0, i"bad shared type $b at $currentAddr when reading ${ctx.owner.ownersIterator.toList}%, %") untpd.TypeTree() case _ => untpd.Literal(readConstant(tag)) @@ -1373,7 +1380,11 @@ class TreeUnpickler(reader: TastyReader, untpd.TypedSplice(readTerm()) case FUNCTION => val body = readUntyped() - val params = until(end)(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()) From 5056b88d85e1d8758a302a8671b0fe213d2095b8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 3 Jun 2018 17:21:25 +0200 Subject: [PATCH 16/64] Prepare to have both inlined and transparent methods All necessary changes except for those in the Inliner itself. --- .../dotty/tools/dotc/ast/TreeTypeMap.scala | 6 ++-- compiler/src/dotty/tools/dotc/ast/tpd.scala | 4 +-- .../tools/dotc/core/tasty/TreePickler.scala | 4 +-- .../tools/dotc/core/tasty/TreeUnpickler.scala | 5 ++-- .../dotty/tools/dotc/typer/Implicits.scala | 29 +++++++++++-------- .../src/dotty/tools/dotc/typer/Inliner.scala | 12 +++++++- .../src/dotty/tools/dotc/typer/Namer.scala | 1 + .../src/dotty/tools/dotc/typer/Typer.scala | 14 +++++++-- 8 files changed, 52 insertions(+), 23 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala index ccadcf15d4da..c75bdb2a9cf8 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -95,7 +95,9 @@ 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 => + if (res.symbol.isTransparentMethod) ann.derivedAnnotation(transform(ann.tree)) + else ann.derivedAnnotation(res.rhs) case ann => ann } res @@ -126,7 +128,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/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index dec1e47f258c..cd842a16f77c 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -20,6 +20,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 = @@ -471,8 +473,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } else foldOver(sym, tree) } - case class UntypedSplice(splice: untpd.Tree) extends Tree - override val cpy: TypedTreeCopier = // Type ascription needed to pick up any new members in TreeCopier (currently there are none) new TypedTreeCopier diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 4175aa41f3b0..b1e6b87e9a78 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -637,8 +637,8 @@ 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 _ => ann.symbol == defn.BodyAnnot && owner.isInlinedMethod + // bodies of inlined (but not transparent) methods are reconstituted automatically when unpickling } def pickleAnnotation(owner: Symbol, ann: Annotation)(implicit ctx: Context) = diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 9e5642f14b3d..aa206e3e173d 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -636,8 +636,9 @@ class TreeUnpickler(reader: TastyReader, val end = readEnd() val tp = readType() val lazyAnnotTree = readLaterWithOwner(end, rdr => ctx => rdr.readTerm()(ctx)) - - owner => + if (tp.isRef(defn.BodyAnnot)) + LazyBodyAnnotation(implicit ctx => lazyAnnotTree(owner).complete) + else Annotation.deferredSymAndTree( implicit ctx => tp.typeSymbol, implicit ctx => lazyAnnotTree(owner).complete) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 31fd2d2026e5..a57534cba51b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -1103,19 +1103,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)) + Inliner.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/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 83a351b2b954..6c776cb02678 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -27,10 +27,20 @@ import collection.mutable import transform.TypeUtils._ import reporting.trace import util.Positions.Position +import util.Property object Inliner { 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] + + def markContextualImplicit(tree: Tree)(implicit ctx: Context): Unit = + methPart(tree).putAttachment(ContextualImplicit, ()) + class InlineAccessors extends AccessProxies { /** If an inline accessor name wraps a unique inline name, this is taken as indication @@ -212,7 +222,7 @@ object Inliner { * to have the inlined method as owner. */ def registerInlineInfo( - inlined: Symbol, treeExpr: Context => Tree)(implicit ctx: Context): Unit = { + 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 => diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 1463a72ca8ab..83a76159d192 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -826,6 +826,7 @@ class Namer { typer: Typer => case original: untpd.DefDef if sym.isInlineableMethod => Inliner.registerInlineInfo( sym, + original.rhs, implicit ctx => typedAheadExpr(original).asInstanceOf[tpd.DefDef].rhs )(localContext(sym)) case _ => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 354c5ba99166..734ced47ea8e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1432,7 +1432,7 @@ class Typer extends Namer val rhs1 = normalizeErasedRhs(typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx), sym) // Overwrite inline body to make sure it is not evaluated twice - if (sym.isInlineableMethod) Inliner.registerInlineInfo(sym, _ => rhs1) + if (sym.isInlineableMethod) Inliner.registerInlineInfo(sym, ddef.rhs, _ => rhs1) if (sym.isConstructor && !sym.isPrimaryConstructor) for (param <- tparams1 ::: vparamss1.flatten) @@ -1902,7 +1902,17 @@ class Typer extends Namer case none => typed(mdef) match { case mdef1: DefDef if Inliner.hasBodyToInline(mdef1.symbol) => - buf += inlineExpansion(mdef1) + if (mdef1.symbol.isInlinedMethod) { + buf += inlineExpansion(mdef1) + // replace body with expansion, because it will be used as inlined body + // from separately compiled files - the original BodyAnnotation is not kept. + } + else { + assert(mdef.symbol.isTransparentMethod) + 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 { From 6762e68c0d0ad80be137fd279d03d708742c27dd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 3 Jun 2018 18:55:00 +0200 Subject: [PATCH 17/64] Dual purpose inliner The new inliner inlines both inline methods with fully-typed expansions and transparent methods with partially untyped extensions. --- .../src/dotty/tools/dotc/typer/Inliner.scala | 225 +++++++++++++++--- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/run/typelevel.scala | 12 +- 3 files changed, 196 insertions(+), 43 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 6c776cb02678..931b7339ecd1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -231,13 +231,125 @@ object Inliner { 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) + val rawBody = treeExpr(ctx) + val typedBody = + if (ctx.reporter.hasErrors) rawBody + else ctx.compilationUnit.inlineAccessors.makeInlineable(rawBody) + val inlineableBody = + if (inlined.isInlinedMethod) typedBody + else 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. + */ + def addReferences(inlineMethod: Symbol, + original: untpd.Tree, typed: tpd.Tree)(implicit ctx: Context): tpd.Tree = { + + def isExternal(sym: Symbol) = sym.exists && !isLocal(sym, inlineMethod) + + // Maps from positions to external reference types and inline selector names. + object referenced extends TreeTraverser { + val typeAtPos = mutable.Map[Position, Type]() + val nameAtPos = mutable.Map[Position, Name]() + val implicitSyms = mutable.Set[Symbol]() + val implicitRefs = new mutable.ListBuffer[Tree] + def registerIfContextualImplicit(tree: Tree) = tree match { + case tree: RefTree + if tree.removeAttachment(ContextualImplicit).isDefined && + isExternal(tree.symbol) && + !implicitSyms.contains(tree.symbol) => + 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 { + implicitSyms += tree.symbol + implicitRefs += tree + } + case _ => + } + def traverse(tree: Tree)(implicit ctx: Context): Unit = { + tree match { + case _: Ident | _: This => + //println(i"leaf: $tree at ${tree.pos}") + if (isExternal(tree.symbol)) { + inlining.println(i"type at pos ${tree.pos.toSynthetic} = ${tree.tpe}") + typeAtPos(tree.pos.toSynthetic) = tree.tpe + } + case _: Select if tree.symbol.name.is(InlineAccessorName) => + inlining.println(i"accessor: $tree at ${tree.pos}") + nameAtPos(tree.pos.toSynthetic) = tree.symbol.name + 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 adjustName(name: Name) = referenced.nameAtPos.get(tree.pos.toSynthetic) match { + case Some(n) => n + case none => name + } + 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 + } + val tree1 = super.transform(tree) + tree1 match { + case This(_) => + adjustLeaf(tree1) + case Ident(name) => + adjustQualifier(adjustLeaf(cpy.Ident(tree1)(adjustName(name)))) + case Select(pre, name) => + cpy.Select(tree1)(pre, adjustName(name)) + 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 = UniqueName.fresh(iref.symbol.name.asTermName), + flags = Implicit | Method | Stable, + info = iref.symbol.info.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) + } + /** `sym` has an inline method with a known body to inline (note: definitions coming * from Scala2x class files might be `@inline`, but still lack that body. */ @@ -499,17 +611,38 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case _ => tree }} - val inlineCtx = inlineContext(call) + val inlineTyper = if (meth.isTransparentMethod) new InlineTyper else new InlineReTyper + val inlineCtx = inlineContext(call).fresh.setTyper(inlineTyper).setNewScope + // 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)) + 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 + inlineCtx.enter(idef.symbol) + } + expansion + case tpd.UntypedSplice(expansion) => + expansion + case expansion => + expansion + } + trace(i"inlining $call\n, BINDINGS =\n${bindingsBuf.toList}%\n%\nEXPANSION =\n$expansion", inlining, show = true) { // The final expansion runs a typing pass over the inlined tree. See InlineTyper for details. - val expansion1 = InlineTyper.typed(expansion, pt)(inlineCtx) + val expansion1 = inlineTyper.typed(expansion, pt)(inlineCtx) /** All bindings in `bindingsBuf` except bindings of inlineable closures */ val bindings = bindingsBuf.toList.map(_.withPos(call.pos)) @@ -529,8 +662,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { */ private object InlineableArg { lazy val paramProxies = paramProxy.values.toSet - def unapply(tree: Ident)(implicit ctx: Context): Option[Tree] = - if (paramProxies.contains(tree.tpe)) + def unapply(tree: Trees.Ident[_])(implicit ctx: Context): Option[Tree] = + if (paramProxies.contains(tree.typeOpt)) bindingsBuf.find(_.name == tree.name) match { case Some(vdef: ValDef) if vdef.symbol.is(Inline) => Some(vdef.rhs.changeOwner(vdef.symbol, ctx.owner)) @@ -541,46 +674,24 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { else None } - /** A typer for inlined code. Its purpose is: + /** A base trait of InlineTyper and InlineReTyper containing operations that + * work the same way for both. Beyond typing or retyping, 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 { - - override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = { - tpe match { - case tpe @ TypeRef(pre, _) if !tpe.symbol.isAccessibleFrom(pre, superAccess) => - tpe.info match { - case TypeAlias(alias) => return ensureAccessible(alias, superAccess, pos) - case _ => - } - case _ => - } - 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 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 - } + trait InlineTyping extends Typer { 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) + var selected = typed(if (condVal) tree.thenp else tree.elsep, pt) + if (selected.isEmpty) selected = tpd.Literal(Constant(())) if (isIdempotentExpr(cond1)) selected else Block(cond1 :: Nil, selected) case _ => @@ -628,6 +739,48 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } } + /** A full typer used for transparent methods */ + private class InlineTyper extends Typer with InlineTyping { + + override def typedTypedSplice(tree: untpd.TypedSplice)(implicit ctx: Context): Tree = + tree.splice match { + case InlineableArg(rhs) => inlining.println(i"inline arg $tree -> $rhs"); rhs + case _ => super.typedTypedSplice(tree) + } + } + + /** A re-typer used for inlined methods */ + private class InlineReTyper extends ReTyper with InlineTyping { + + override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = { + tpe match { + case tpe @ TypeRef(pre, _) if !tpe.symbol.isAccessibleFrom(pre, superAccess) => + tpe.info match { + case TypeAlias(alias) => return ensureAccessible(alias, superAccess, pos) + case _ => + } + case _ => + } + 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 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 newLikeThis: Typer = new InlineReTyper + } + /** Drop any side-effect-free bindings that are unused in expansion or other reachable bindings. * Inline def bindings that are used only once. */ diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 734ced47ea8e..38acb3b4f537 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1908,7 +1908,7 @@ class Typer extends Namer // from separately compiled files - the original BodyAnnotation is not kept. } else { - assert(mdef.symbol.isTransparentMethod) + assert(mdef1.symbol.isTransparentMethod, 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. diff --git a/tests/run/typelevel.scala b/tests/run/typelevel.scala index 9a836565ef51..737943b59753 100644 --- a/tests/run/typelevel.scala +++ b/tests/run/typelevel.scala @@ -17,14 +17,14 @@ object Test extends App { } 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) From 3617860330f60d35e6dfc1c2f1c0f0f9e51f4f27 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 3 Jun 2018 21:52:01 +0200 Subject: [PATCH 18/64] Two fixes to transparent handling and more tests --- .../src/dotty/tools/dotc/typer/Inliner.scala | 28 +- tests/run/lst-transparent/Lst.scala | 496 ++++++++++++++++++ tests/run/lst-transparent/LstTest.scala | 329 ++++++++++++ tests/run/transparent-implicits.check | 1 + tests/run/transparent-implicits.scala | 26 + tests/run/transparent-object.check | 1 + tests/run/transparent-object.scala | 14 + tests/run/transparent.check | 9 + tests/run/transparent/Test_2.scala | 21 + tests/run/transparent/inlines_1.scala | 41 ++ tests/run/transparentAccess/C_1.scala | 7 + tests/run/transparentAccess/Test_2.scala | 7 + tests/run/transparentArrowAssoc.scala | 24 + tests/run/transparentAssign.scala | 24 + tests/run/transparentByName.scala | 37 ++ tests/run/transparentForeach.check | 137 +++++ tests/run/transparentForeach.scala | 67 +++ tests/run/transparentPower.check | 2 + tests/run/transparentPower/Test_2.scala | 9 + tests/run/transparentPower/power_1.scala | 12 + tests/run/transparentPrivates.scala | 36 ++ tests/run/transparentProtected.scala | 22 + 22 files changed, 1337 insertions(+), 13 deletions(-) create mode 100644 tests/run/lst-transparent/Lst.scala create mode 100644 tests/run/lst-transparent/LstTest.scala create mode 100644 tests/run/transparent-implicits.check create mode 100644 tests/run/transparent-implicits.scala create mode 100644 tests/run/transparent-object.check create mode 100644 tests/run/transparent-object.scala create mode 100644 tests/run/transparent.check create mode 100644 tests/run/transparent/Test_2.scala create mode 100644 tests/run/transparent/inlines_1.scala create mode 100644 tests/run/transparentAccess/C_1.scala create mode 100644 tests/run/transparentAccess/Test_2.scala create mode 100644 tests/run/transparentArrowAssoc.scala create mode 100644 tests/run/transparentAssign.scala create mode 100644 tests/run/transparentByName.scala create mode 100644 tests/run/transparentForeach.check create mode 100644 tests/run/transparentForeach.scala create mode 100644 tests/run/transparentPower.check create mode 100644 tests/run/transparentPower/Test_2.scala create mode 100644 tests/run/transparentPower/power_1.scala create mode 100644 tests/run/transparentPrivates.scala create mode 100644 tests/run/transparentProtected.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 931b7339ecd1..1cc4c273bb09 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -70,7 +70,9 @@ object Inliner { 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) + val setter = useSetter(lhs) + if (inlineSym.isTransparentMethod) tree // just generate a setter, but don't integrate it in the tree + else cpy.Apply(tree)(setter, rhs :: Nil) case _ => tree } @@ -686,6 +688,18 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { */ trait InlineTyping extends Typer { + override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = { + tpe match { + case tpe @ TypeRef(pre, _) if !tpe.symbol.isAccessibleFrom(pre, superAccess) => + tpe.info match { + case TypeAlias(alias) => return ensureAccessible(alias, superAccess, pos) + case _ => + } + case _ => + } + super.ensureAccessible(tpe, superAccess, pos) + } + override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = { val cond1 = typed(tree.cond, defn.BooleanType) cond1.tpe.widenTermRefExpr match { @@ -752,18 +766,6 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { /** A re-typer used for inlined methods */ private class InlineReTyper extends ReTyper with InlineTyping { - override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = { - tpe match { - case tpe @ TypeRef(pre, _) if !tpe.symbol.isAccessibleFrom(pre, superAccess) => - tpe.info match { - case TypeAlias(alias) => return ensureAccessible(alias, superAccess, pos) - case _ => - } - case _ => - } - 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 diff --git a/tests/run/lst-transparent/Lst.scala b/tests/run/lst-transparent/Lst.scala new file mode 100644 index 000000000000..9e0f52076c0d --- /dev/null +++ b/tests/run/lst-transparent/Lst.scala @@ -0,0 +1,496 @@ +package dotty.tools.dotc +package util + +import printing.{Printer, Texts} +import Texts.Text +import collection.mutable.{ListBuffer, StringBuilder} + + +/** A lightweight class for lists, optimized for short and medium lengths. + * A list is represented at runtime as + * + * If it is empty: the value `Lst.Empty` + * If it contains one element: the element itself + * If it contains more elements: an Array[Any] containing the elements + */ +class Lst[+T](val elems: Any) extends AnyVal { + import Lst._ + + def length: Int = elems match { + case null => 0 + case elems: Arr => elems.length + case elem => 1 + } + + def isEmpty = elems == null + def nonEmpty = elems != null + + transparent def foreach(op: => T => Unit): Unit = { + def sharedOp(x: T) = op(x) + elems match { + case null => + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < elems.length) { sharedOp(elem(i)); i += 1 } + case elem: T @ unchecked => sharedOp(elem) + } + } + + /** Like `foreach`, but completely inlines `op`, at the price of generating the code twice. + * Should be used only of `op` is small + */ + transparent 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) + } + + def iterator(): Iterator[T] = elems match { + case null => Iterator.empty + case elems: Arr => elems.iterator.asInstanceOf[Iterator[T]] + case elem: T @unchecked => Iterator.single(elem) + } + + def copyToArray[U >: T](target: Array[U], from: Int) = elems match { + case null => + case elems: Arr => System.arraycopy(elems, 0, target, from, elems.length) + case elem: T @ unchecked => target(from) = elem + } + + /** `f` is pulled out, not duplicated */ + transparent def map[U](f: => T => U): Lst[U] = { + def op(x: T) = f(x) + elems match { + case null => Empty + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + val newElems = new Arr(elems.length) + var i = 0 + while (i < elems.length) { newElems(i) = op(elem(i)); i += 1 } + new Lst[U](newElems) + case elem: T @ unchecked => new Lst[U](op(elem)) + } + } + + def mapConserve[U](f: T => U): Lst[U] = elems match { + case null => Empty + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var newElems: Arr = null + var i = 0 + while (i < elems.length) { + val x = elem(i) + val y = f(x) + if (newElems != null) newElems(i) = y + else if (!eq(x, y)) { + newElems = new Arr(elems.length) + System.arraycopy(elems, 0, newElems, 0, i) + newElems(i) = y + } + i += 1 + } + if (newElems == null) this.asInstanceOf[Lst[U]] else new Lst[U](newElems) + case elem: T @ unchecked => new Lst[U](f(elem)) + } + + def flatMap[U](f: T => Lst[U]): Lst[U] = elems match { + case null => Empty + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + val newElemss: Arr = new Arr(elems.length) + var i = 0 + var len = 0 + while (i < elems.length) { + val ys = f(elem(i)) + len += ys.length + newElemss(i) = ys.elems + i += 1 + } + if (len == 0) Empty + else if (len == 1) { + i = 0 + while (newElemss(i) == null) i += 1 + new Lst[U](newElemss(i)) + } + else { + val newElems = new Arr(len) + i = 0 + var j = 0 + while (i < newElemss.length) { + val ys = new Lst[U](newElemss(i)) + ys.copyToArray(newElems, j) + j += ys.length + i += 1 + } + new Lst[U](newElems) + } + case elem: T @ unchecked => new Lst[U](f(elem).elems) + } + + def filter(p: T => Boolean): Lst[T] = elems match { + case null => Empty + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + val scratch = new Arr(elems.length) + var i = 0 + var len = 0 + while (i < elems.length) { + val x = elem(i) + if (p(x)) { scratch(len) = x; len += 1 } + i += 1 + } + if (len == elems.length) this + else _fromArray(scratch, 0, len) + case elem: T @unchecked => + if (p(elem)) this else Empty + } + def filterNot(p: T => Boolean): Lst[T] = filter(!p(_)) + + transparent def exists(p: => T => Boolean): Boolean = { + def op(x: T) = p(x) + elems match { + case null => false + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < elems.length && !op(elem(i))) i += 1 + i < elems.length + case elem: T @unchecked => + op(elem) + } + } + + transparent def forall(p: => T => Boolean): Boolean = { + def op(x: T) = p(x) + elems match { + case null => true + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < elems.length && op(elem(i))) i += 1 + i == elems.length + case elem: T @unchecked => + op(elem) + } + } + + transparent 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 + while (i < elems.length && elem(i) != x) i += 1 + i < elems.length + case elem: T @unchecked => + elem == x + } + + transparent def foldLeft[U](z: U)(f: => (U, T) => U) = { + def op(x: U, y: T) = f(x, y) + elems match { + case null => z + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + var acc = z + while (i < elems.length) { acc = op(acc, elem(i)); i += 1 } + acc + case elem: T @unchecked => + op(z, elem) + } + } + + transparent def /: [U](z: U)(op: => (U, T) => U) = foldLeft(z)(op) + + def reduceLeft[U >: T](op: (U, U) => U) = elems match { + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 1 + var acc: U = elem(0) + while (i < elems.length) { acc = op(acc, elem(i)); i += 1 } + acc + case elem: T @unchecked => + elem + } + + def reverse: Lst[T] = elems match { + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + val newElems = new Arr(elems.length) + var i = 0 + while (i < elems.length) { + newElems(elems.length - 1 - i) = elem(i) + i += 1 + } + new Lst[T](newElems) + case _ => this + } + + def apply(n: Int): T = elems match { + case null => throw new IndexOutOfBoundsException(n.toString) + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + elem(n) + case elem: T @unchecked => + if (n == 0) elem else throw new IndexOutOfBoundsException(n.toString) + } + + def head: T = apply(0) + def last: T = apply(length - 1) + + def headOr[U >: T](alt: => U): U = if (isEmpty) alt else head + + def slice(start: Int, end: Int): Lst[T] = + if (start < 0) slice(0, end) + else elems match { + case null => this + case elems: Arr => _fromArray(elems, start, end `min` elems.length) + case elem: T @ unchecked => if (end == 0) Empty else this + } + + def drop(n: Int): Lst[T] = slice(n, length) + def tail = drop(1) + def take(n: Int): Lst[T] = slice(0, n) + + def ++ [U >: T](that: Lst[U]): Lst[U] = + if (elems == null) that + else if (that.elems == null) this + else { + val len1 = this.length + val len2 = that.length + val newElems = new Arr(len1 + len2) + this.copyToArray(newElems, 0) + that.copyToArray(newElems, len1) + new Lst[U](newElems) + } + + def zipWith[U, V](that: Lst[U])(op: (T, U) => V): Lst[V] = + this.elems match { + case null => Empty + case elems1: Arr => def elem1(i: Int) = elems1(i).asInstanceOf[T] + that.elems match { + case null => Empty + case elems2: Arr => def elem2(i: Int) = elems2(i).asInstanceOf[U] + val len = elems1.length min elems2.length + if (len == 0) Empty + else if (len == 1) new Lst[V](op(elem1(0), elem2(0))) + else { + var newElems: Arr = null + var i = 0 + while (i < len) { + val x = elem1(i) + val y = op(x, elem2(i)) + if (newElems != null) newElems(i) = y + else if (!eq(x, y)) { + newElems = new Arr(len) + System.arraycopy(elems, 0, newElems, 0, i) + newElems(i) = y + } + i += 1 + } + new Lst[V](newElems) + } + case elem2: U @unchecked => + new Lst[V](op(elem1(0), elem2)) + } + case elem1: T @unchecked => + that.elems match { + case null => Empty + case elems2: Arr => def elem2(i: Int) = elems2(i).asInstanceOf[U] + new Lst[V](op(elem1, elem2(0))) + case elem2: U @unchecked => new Lst[V](op(elem1, elem2)) + } + } + + def zip[U](that: Lst[U]): Lst[(T, U)] = zipWith(that)((_, _)) + + def zipWithIndex: Lst[(T, Int)] = elems match { + case null => Empty + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + val newElems = new Arr(elems.length) + var i = 0 + while (i < elems.length) { newElems(i) = (elem(i), i); i += 1 } + new Lst[(T, Int)](newElems) + case elem: T @unchecked => + new Lst[(T, Int)]((elem, 0)) + } + + def corresponds[U](that: Lst[U])(p: (T, U) => Boolean): Boolean = + (this `eqLst` that) || { + this.elems match { + case null => + that.elems == null + case elems1: Arr => def elem1(i: Int) = elems1(i).asInstanceOf[T] + that.elems match { + case elems2: Arr => def elem2(i: Int) = elems2(i).asInstanceOf[U] + val len = elems1.length + len == elems2.length && { + var i = 0 + while (i < len && p(elem1(i), elem2(i))) i += 1 + i == len + } + case _ => false + } + case elem1: T @unchecked => + that.elems match { + case null => false + case elems2: Arr => false + case elem2: U @unchecked => p(elem1, elem2) + } + } + } + + def === [U](that: Lst[U]) = + (this `eqLst` that) || { + elems match { + case elems1: Arr => + that.elems match { + case elems2: Arr => + val len = elems1.length + len == elems2.length && { + var i = 0 + while (i < len && elems1(i).equals(elems2(i))) i += 1 + i == len + } + case _ => false + } + case elem => elem == that.elems + } + } + + def eqLst[U](that: Lst[U]) = eq(elems, that.elems) + + def eqElements[U](that: Lst[U]): Boolean = corresponds(that)(eqFn) + + def mkString: String = mkString(", ") + + def mkString(sep: String): String = mkString("", sep, "") + def mkString(left: String, sep: String, right: String): String = { + val b = new StringBuilder(left) + var first = true + foreachInlined { elem => + if (first) first = false else b ++= sep + b ++= elem.toString + } + b ++= right + b.toString + } + + override def toString = mkString("Lst(", ", ", ")") +} + +object Lst { + + private type Arr = Array[Any] + + private def eq(x: Any, y: Any) = x.asInstanceOf[AnyRef] `eq` y.asInstanceOf[AnyRef] + private val eqFn = (x: Any, y: Any) => eq(x, y) + + val Empty = new Lst[Nothing](null) + + def apply[T](): Lst[T] = Empty + + def apply[T](x0: T): Lst[T] = new Lst[T](x0) + + def apply[T](x0: T, x1: T): Lst[T] = { + val elems = new Arr(2) + elems(0) = x0 + elems(1) = x1 + new Lst[T](elems) + } + + def apply[T](x0: T, x1: T, x2: T): Lst[T] = { + val elems = new Arr(3) + elems(0) = x0 + elems(1) = x1 + elems(2) = x2 + new Lst[T](elems) + } + + def apply[T](x0: T, x1: T, x2: T, x3: T): Lst[T] = { + val elems = new Arr(4) + elems(0) = x0 + elems(1) = x1 + elems(2) = x2 + elems(3) = x3 + new Lst[T](elems) + } + + def apply[T](x0: T, x1: T, x2: T, x3: T, x4: T, xs: T*): Lst[T] = { + val elems = new Arr(5 + xs.length) + elems(0) = x0 + elems(1) = x1 + elems(2) = x2 + elems(3) = x3 + elems(4) = x4 + xs.copyToArray(elems, 5) + new Lst[T](elems) + } + + def fill[T](n: Int)(elem: => T) = { + val elems = new Arr(n) + var i = 0 + while (i < n) { elems(i) = elem; i += 1} + new Lst[T](elems) + } + + class Buffer[T] { + private var len = 0 + private var elem: T = _ + private var elems: Arr = _ + + def size = len + + /** pre: len > 0, n > 1 */ + private def ensureSize(n: Int) = + if (len == 1) { + elems = new Arr(n `max` 16) + elems(0) = elem + } + else if (len < n) { + val newLen = n `max` len << 2 + val newElems = new Arr(newLen) + System.arraycopy(elems, 0, newElems, 0, len) + elems = newElems + } + + def += (x: T): this.type = { + if (len == 0) elem = x + else { + ensureSize(len + 1) + elems(len) = x + } + len += 1 + this + } + + def ++= (xs: Lst[T]): this.type = { + xs.elems match { + case null => this + case elems2: Arr => + if (len == 0) elems = elems2 + else { + ensureSize(len + elems2.length) + System.arraycopy(elems2, 0, elems, len, elems2.length) + } + len += elems2.length + case elem: T @unchecked => this += elem + } + this + } + + def toLst: Lst[T] = + if (len == 0) Empty + else if (len == 1) new Lst[T](elem) + else _fromArray(elems, 0, len) + + def clear() = + len = 0 + } + + private def _fromArray[T](elems: Arr, start: Int, end: Int): Lst[T] = { + val len = end - start + if (len <= 0) Empty + else if (len == 1) new Lst[T](elems(start)) + else if (start == 0 && end == elems.length) new Lst[T](elems) + else { + val newElems = new Arr(len) + System.arraycopy(elems, start, newElems, 0, len) + new Lst[T](newElems) + } + } + + def fromArray[T](elems: Array[T], start: Int, end: Int): Lst[T] = + _fromArray(elems.asInstanceOf[Arr], start, end) +} diff --git a/tests/run/lst-transparent/LstTest.scala b/tests/run/lst-transparent/LstTest.scala new file mode 100644 index 000000000000..87f2541fb62a --- /dev/null +++ b/tests/run/lst-transparent/LstTest.scala @@ -0,0 +1,329 @@ +object Test extends App { + import dotty.tools.dotc.util.Lst + + val xs0: Lst[String] = Lst.Empty + val xs1 = Lst("a") + val xs2 = Lst("a", "b") + val xs3 = Lst("a", "b", "c") + val xs4 = Lst("a", "b", "c", "d") + val xs5 = Lst("a", "b", "c", "d", "e") + val xs10 = xs5 ++ xs5 + + val is0: Lst[Int] = Lst.Empty + val is1 = Lst(1) + val is2 = Lst(1, 2) + val is3 = Lst(1, 2, 3) + val is4 = Lst(1, 2, 3, 4) + val is5 = Lst(1, 2, 3, 4, 5) + val is10 = is5 ++ is5 + + def lengthTest() = { + assert(xs0.length == 0) + assert(xs1.length == 1) + assert(xs2.length == 2) + assert(xs10.length == 10) + + assert(is0.length == 0) + assert(is1.length == 1) + assert(is2.length == 2) + assert(is10.length == 10) + } + + def concatTest() = { + assert(xs1 ++ xs0 == xs1) + assert(xs0 ++ xs1 == xs1) + assert(xs3 ++ xs0 == xs3) + assert(xs0 ++ xs4 == xs4) + + assert(is1 ++ is0 == is1) + assert(is0 ++ is1 == is1) + assert(is3 ++ is0 == is3) + assert(is0 ++ is4 == is4) + } + + def foreachTest() = { + xs0.foreach(s => assert(s.length == 1)) + xs3.foreach(s => assert(s.length == 1)) + def test1() = { + var x = 0 + xs10.foreach(elem => x += elem.length) + } + def test2() = { + var y = 0 + xs10.foreachInlined(elem => y += elem.length) + } + test1() + test2() + + is0.foreach(i => assert(i == 1)) + is3.foreach(i => assert(i <= 3)) + } + + def mapTest() = { + val ys0 = xs0.map(_.reverse) + val ys1 = xs1.map(s => s + s) + assert(ys1.mkString == "aa") + val ys5 = xs5.map(s => s + s) + assert(ys5.mkString == "aa, bb, cc, dd, ee") + + val js0 = is0.map(i => i * i) + val js1 = is1.map(i => i + i) + assert(js1.mkString == "2") + val js5 = is5.map(s => s + s) + assert(js5.mkString == "2, 4, 6, 8, 10") + } + + def mapConserveTest() = { + val ys0 = xs0.mapConserve(_.reverse) + val ys1 = xs1.mapConserve(s => s + s) + assert(ys1.mkString == "aa") + val ys2 = xs2.mapConserve(identity) + assert(ys2 `eqLst` xs2) + val ys5 = xs5.map(s => s + s) + assert(ys5.mkString == "aa, bb, cc, dd, ee") + val ys4 = xs4.mapConserve(s => if (s == "c") "cc" else s) + assert(ys4.mkString == "a, b, cc, d") + + val js0 = is0.mapConserve(i => i * i) + val js1 = is1.mapConserve(s => s + s) + assert(js1.mkString == "2") + val js2 = is2.mapConserve(identity) + assert(js2 `eqLst` is2) + val js5 = is5.map(s => s + s) + assert(js5.mkString == "2, 4, 6, 8, 10") + val js4 = is4.mapConserve(s => if (s == 3) -3 else s) + assert(js4.mkString == "1, 2, -3, 4") + } + + def flatMapTest() = { + val ys0 = xs0.flatMap(s => Lst(s, s)) + assert(ys0.isEmpty) + val ys2 = xs2.flatMap(s => Lst(s, s)) + assert(ys2.mkString == "a, a, b, b") + val ys2a = xs2.flatMap(_ => Lst.Empty) + assert(ys2a.isEmpty) + val ys4 = xs4.flatMap(s => Lst.fill(s.head - 'a')(s)) + assert(ys4.mkString == "b, c, c, d, d, d") + val ys5 = xs5.flatMap(s => if s == "c" then Lst(s) else Lst()) + assert(ys5 == Lst("c")) + + val js0 = is0.flatMap(s => Lst(s, s)) + assert(js0.isEmpty) + val js2 = is2.flatMap(s => Lst(s, s)) + assert(js2.mkString == "1, 1, 2, 2") + val js2a = is2.flatMap(_ => Lst.Empty) + assert(js2a.isEmpty) + val js4 = is4.flatMap(s => Lst.fill(s - 1)(s)) + assert(js4.mkString == "2, 3, 3, 4, 4, 4", js4) + val js5 = is5.flatMap(s => if s == 3 then Lst(-3) else Lst()) + assert(js5 == Lst(-3)) + } + + def filterTest() = { + val ys0 = xs0.filter(_.head >= 'c') + assert(ys0.isEmpty) + val ys1 = xs1.filter(_.head >= 'c') + assert(ys1.isEmpty) + val ys1a = xs1.filterNot(_.head >= 'c') + assert(ys1a `eqLst` xs1) + val ys5 = xs5.filter(_.head % 2 != 0) + assert(ys5 === Lst("a", "c", "e"), ys5) + + val js0 = is0.filter(_ > 3) + assert(js0.isEmpty) + val js1 = is1.filter(_ > 3) + assert(js1.isEmpty) + val js1a = is1.filterNot(_ > 3) + assert(js1a `eqLst` is1) + val js5 = is5.filter(_ % 2 != 0) + assert(js5 === Lst(1, 3, 5), js5) + } + + def existsTest() = { + assert(!xs0.exists(_ => true)) + assert(xs1.exists(_ == "a")) + assert(xs5.exists(_ == "c")) + assert(!xs5.exists(_.head > 'e')) + + assert(!is0.exists(_ => true)) + assert(is1.exists(_ == 1)) + assert(is5.exists(_ == 3)) + assert(!is5.exists(_ > 5)) + } + + def forallTest() = { + assert(xs0.forall(_ => false)) + assert(xs1.forall(_ == "a")) + assert(xs5.forall(_.head <= 'e')) + assert(!xs5.forall(_ == "c")) + } + + def containsTest() = { + assert(!xs0.contains("")) + assert(xs1.contains("a")) + assert(xs10.contains("e")) + assert(!xs10.contains("f")) + + assert(!is0.contains(2)) + assert(is1.contains(1)) + assert(is10.contains(5)) + assert(!is10.contains(6)) + } + + def foldTest() = { + assert(xs0.foldLeft("x")(_ ++ _) == "x") + assert(xs1.foldLeft("x")(_ ++ _) == "xa") + assert(xs2.foldLeft("x")(_ ++ _) == "xab") + assert(xs3.foldLeft("x")(_ ++ _) == "xabc") + assert(("x" /: xs0)(_ ++ _) == "x") + assert(("x" /: xs1)(_ ++ _) == "xa") + assert(("x" /: xs2)(_ ++ _) == "xab") + assert(("x" /: xs3)(_ ++ _) == "xabc") + assert(xs1.reduceLeft(_ + _) == "a") + assert(xs3.reduceLeft(_ + _) == "abc") + + assert(is0.foldLeft(3)(_ + _) == 3) + assert(is1.foldLeft(3)(_ + _) == 4) + assert(is2.foldLeft(3)(_ + _) == 6) + assert(is3.foldLeft(3)(_ + _) == 9) + assert((3 /: is0)(_ + _) == 3) + assert((3 /: is1)(_ + _) == 4) + assert((3 /: is2)(_ + _) == 6) + assert((3 /: is3)(_ + _) == 9) + assert(is1.reduceLeft(_ + _) == 1) + assert(is3.reduceLeft(_ + _) == 6) + } + + def reverseTest() = { + assert(xs0.reverse === xs0) + assert(xs1.reverse === xs1) + assert(xs3.reverse.mkString == "c, b, a", xs3.reverse.mkString) + assert(xs4.reverse.reverse === xs4, xs4.reverse.reverse) + } + + def applyTest() = { + assert(xs5.head == "a") + assert(xs5.last == "e") + assert(xs5(3) == "d") + assert(xs1(0) == "a") + + assert(is5.head == 1) + assert(is5.last == 5) + assert(is5(3) == 4) + assert(is1(0) == 1) + } + + def sliceTest() = { + assert(xs5.slice(2, 4) === Lst("c", "d")) + assert(xs5.drop(4) === Lst("e")) + assert(xs5.take(4).mkString("") == "abcd") + assert(xs5.drop(-1) `eqLst` xs5) + assert(xs1.take(1) `eqLst` xs1) + assert(xs0.take(10).length == 0) + + assert(is5.slice(2, 4) === Lst(3, 4)) + assert(is5.drop(4) === Lst(5)) + assert(is5.take(4).mkString("") == "1234") + assert(is5.drop(-1) `eqLst` is5) + assert(is1.take(1) `eqLst` is1) + assert(is0.take(10).length == 0) + } + + def zipWithTest() = { + val ys4a = xs4.zipWith(xs5)(_ + _) + val ys4b = xs5.zipWith(xs4)(_ + _) + assert(ys4a.mkString("") == "aabbccdd", ys4a) + assert(ys4a === ys4b) + val ys1a = xs1.zipWith(xs1)(_ + _) + assert(ys1a === Lst("aa")) + val ys1b = xs1.zipWith(xs2)(_ + _) + assert(ys1b === Lst("aa")) + val ys1c = xs2.zipWith(xs1)(_ + _) + assert(ys1c === Lst("aa")) + val ys0a = xs1.zipWith(xs0)(_ + _) + val ys0b = xs0.zipWith(xs1)(_ + _) + assert((ys0a ++ ys0b).isEmpty) + val ys3i = xs3.zipWithIndex.map((x, y) => (x, y + 1)) + assert(ys3i === Lst(("a", 1), ("b", 2), ("c", 3)), ys3i) + + val js4a = is4.zipWith(is5)(_ + _) + val js4b = is5.zipWith(is4)(_ + _) + assert(js4a.mkString("") == "2468", js4a) + assert(js4a === js4b) + val js1a = is1.zipWith(is1)(_ + _) + assert(js1a === Lst(2)) + val js1b = is1.zipWith(is2)(_ + _) + assert(js1b === Lst(2)) + val js1c = is2.zipWith(is1)(_ + _) + assert(js1c === Lst(2)) + val js0a = is1.zipWith(is0)(_ + _) + val js0b = is0.zipWith(is1)(_ + _) + assert((js0a ++ js0b).isEmpty) + val js3i = is3.zipWithIndex.map((x, y) => (x, y + 1)) + assert(js3i === Lst((1, 1), (2, 2), (3, 3)), js3i) + assert(js3i.forall(_ == _)) + } + + def correspondsTest() = { + assert(xs4.corresponds(xs4)(_ == _)) + assert(!xs4.corresponds(xs5)(_ == _)) + assert(xs1.corresponds(xs1)(_ == _)) + assert(!xs1.corresponds(Lst("b"))(_ == _)) + assert(xs0.corresponds(xs0)(_ == _)) + assert(!xs0.corresponds(xs1)(_ == _)) + val zs1 = Lst(new Object, new Object) + val zs2 = Lst(zs1(0), zs1(1)) + val zs3 = Lst(new Object, new Object) + assert(zs1.eqElements(zs1)) + assert(zs1.eqElements(zs2)) + assert(!zs1.eqElements(zs3)) + + assert(is4.corresponds(is4)(_ == _)) + assert(!is4.corresponds(is5)(_ == _)) + assert(is1.corresponds(is1)(_ == _)) + assert(!is1.corresponds(Lst(-1))(_ == _)) + assert(is0.corresponds(is0)(_ == _)) + assert(!is0.corresponds(is1)(_ == _)) + } + + def bufferTest() = { + { val b = new Lst.Buffer[String] + b += "a" + assert(b.size == 1) + assert(b.toLst === Lst("a")) + b += "aa" + b ++= Lst.fill(20)("a") + assert(b.toLst.mkString("") == "a" * 23) + assert(b.size == 22) + } + + { val b = new Lst.Buffer[Int] + b += 1 + assert(b.size == 1) + assert(b.toLst === Lst(1)) + b += 11 + b ++= Lst.fill(20)(1) + assert(b.toLst.mkString("") == "1" * 23) + assert(b.size == 22) + } + } + + println("testing") + lengthTest() + concatTest() + foreachTest() + mapTest() + mapConserveTest() + flatMapTest() + filterTest() + existsTest() + forallTest() + containsTest() + foldTest() + reverseTest() + applyTest() + sliceTest() + zipWithTest() + correspondsTest() + bufferTest() +} \ No newline at end of file diff --git a/tests/run/transparent-implicits.check b/tests/run/transparent-implicits.check new file mode 100644 index 000000000000..17a1d370d730 --- /dev/null +++ b/tests/run/transparent-implicits.check @@ -0,0 +1 @@ +(X(),Y()) diff --git a/tests/run/transparent-implicits.scala b/tests/run/transparent-implicits.scala new file mode 100644 index 000000000000..bd41c41eecb9 --- /dev/null +++ b/tests/run/transparent-implicits.scala @@ -0,0 +1,26 @@ +case class X() +case class Y() + +object impl { + implicit val y: Y = new Y() +} + +object inlines { + import impl._ + + class C { + implicit val x: X = new X() + + transparent + def f(): (X, Y) = + (implicitly[X], implicitly[Y]) + } +} + +object Test { + def main(args: Array[String]) = { + val c = new inlines.C + val xy = c.f() + println(xy) + } +} diff --git a/tests/run/transparent-object.check b/tests/run/transparent-object.check new file mode 100644 index 000000000000..5716ca5987cb --- /dev/null +++ b/tests/run/transparent-object.check @@ -0,0 +1 @@ +bar diff --git a/tests/run/transparent-object.scala b/tests/run/transparent-object.scala new file mode 100644 index 000000000000..21d2566022df --- /dev/null +++ b/tests/run/transparent-object.scala @@ -0,0 +1,14 @@ + +object Test { + def main(args: Array[String]): Unit = { + Foo.foo + } +} + +object Foo extends Bar { + transparent def foo: Unit = bar +} + +class Bar { + def bar: Unit = println("bar") +} diff --git a/tests/run/transparent.check b/tests/run/transparent.check new file mode 100644 index 000000000000..5f711274b935 --- /dev/null +++ b/tests/run/transparent.check @@ -0,0 +1,9 @@ +100 +10000 + + Inner +Outer.f +Outer.f Inner + Inner +Outer.f +Outer.f Inner diff --git a/tests/run/transparent/Test_2.scala b/tests/run/transparent/Test_2.scala new file mode 100644 index 000000000000..a2ab2caca22f --- /dev/null +++ b/tests/run/transparent/Test_2.scala @@ -0,0 +1,21 @@ +object Test { + + import p.transparents._ + + def main(args: Array[String]): Unit = { + println(f(10)) + println(f(f(10))) + + track("hello") { println("") } + + val o = new Outer + val i = new o.Inner + println(i.m) + println(i.g) + println(i.h) + println(o.inner.m) + println(o.inner.g) + println(o.inner.h) + } + +} diff --git a/tests/run/transparent/inlines_1.scala b/tests/run/transparent/inlines_1.scala new file mode 100644 index 000000000000..946599b1a196 --- /dev/null +++ b/tests/run/transparent/inlines_1.scala @@ -0,0 +1,41 @@ +package p +import collection.mutable + +object transparents { + + final val monitored = false + + transparent def f(x: Int): Int = x * x + + val hits = new mutable.HashMap[String, Int] { + override def default(key: String): Int = 0 + } + + def record(fn: String, n: Int = 1) = { + if (monitored) { + val name = if (fn.startsWith("member-")) "member" else fn + hits(name) += n + } + } + + @volatile private var stack: List[String] = Nil + + transparent def track[T](fn: String)(op: => T) = + if (monitored) { + stack = fn :: stack + record(fn) + try op + finally stack = stack.tail + } else op + + class Outer { + def f = "Outer.f" + class Inner { + val msg = " Inner" + transparent def m = msg + transparent def g = f + transparent def h = f ++ m + } + val inner = new Inner + } +} 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/transparentAccess/Test_2.scala b/tests/run/transparentAccess/Test_2.scala new file mode 100644 index 000000000000..98ea7693abb7 --- /dev/null +++ b/tests/run/transparentAccess/Test_2.scala @@ -0,0 +1,7 @@ + +object Test { + def main(args: Array[String]) = { + val c = new p.C() + c.inl() + } +} diff --git a/tests/run/transparentArrowAssoc.scala b/tests/run/transparentArrowAssoc.scala new file mode 100644 index 000000000000..80dd0d90d3c3 --- /dev/null +++ b/tests/run/transparentArrowAssoc.scala @@ -0,0 +1,24 @@ +import scala.collection.immutable._ + +import scala.collection.mutable.{ Builder, ListBuffer } + +object Test { + + private val defaultOrdering = Map[Numeric[_], Ordering[_]]( + Numeric.BigIntIsIntegral -> Ordering.BigInt, + Numeric.IntIsIntegral -> Ordering.Int + ) + + final implicit class ArrowAssoc[A](private val self: A) extends AnyVal { + transparent def -> [B](y: B): Tuple2[A, B] = Tuple2(self, y) + def →[B](y: B): Tuple2[A, B] = ->(y) + } + + def main(args: Array[String]): Unit = { + assert((1 -> 2) == (1, 2)) + assert((1 → 2) == (1, 2)) + } + + +} + diff --git a/tests/run/transparentAssign.scala b/tests/run/transparentAssign.scala new file mode 100644 index 000000000000..4fec5fe255be --- /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 + transparent def setX(z: Int) = x = z + transparent 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/transparentByName.scala b/tests/run/transparentByName.scala new file mode 100644 index 000000000000..b45451d4feda --- /dev/null +++ b/tests/run/transparentByName.scala @@ -0,0 +1,37 @@ +object Test { + + class Range(from: Int, end: Int) { + transparent def foreach(op: => Int => Unit): Unit = { + var i = from + while (i < end) { + op(i) + i += 1 + } + } + } + transparent def twice(op: => Int => Unit): Unit = { + op(1) + op(2) + } + transparent def thrice(op: => Unit): Unit = { + op + op + op + } + + def main(args: Array[String]) = { + var j = 0 + new Range(1, 10).foreach(j += _) + assert(j == 45, j) + twice { x => j = j - x } + thrice { j = j + 1 } + val f = new Range(1, 10).foreach + f(j -= _) + assert(j == 0, j) + new Range(1, 10).foreach { i1 => + new Range(2, 11).foreach { i2 => + j += i1 * i2 + } + } + } +} diff --git a/tests/run/transparentForeach.check b/tests/run/transparentForeach.check new file mode 100644 index 000000000000..3fced2fad78d --- /dev/null +++ b/tests/run/transparentForeach.check @@ -0,0 +1,137 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +1 +2 +3 +4 +5 +6 +7 +8 +9 +1 +2 +3 +4 +5 +6 +7 +8 +9 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +3 +3 +3 +3 +3 +3 +3 +3 +3 +3 +4 +4 +4 +4 +4 +4 +4 +4 +4 +4 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +6 +6 +6 +6 +6 +6 +6 +6 +6 +6 +7 +7 +7 +7 +7 +7 +7 +7 +7 +7 +8 +8 +8 +8 +8 +8 +8 +8 +8 +8 +9 +9 +9 +9 +9 +9 +9 +9 +9 +9 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 diff --git a/tests/run/transparentForeach.scala b/tests/run/transparentForeach.scala new file mode 100644 index 000000000000..eda163707093 --- /dev/null +++ b/tests/run/transparentForeach.scala @@ -0,0 +1,67 @@ +object Test { + + class Range(from: Int, end: Int) { + + transparent + def foreach(op: => Int => Unit): Unit = { + var i = from + while (i < end) { + op(i) + i += 1 + } + } + + def filter(p: Int => Boolean): List[Int] = ??? + } + + implicit class intWrapper(private val start: Int) extends AnyVal { + def until(end: Int) = new Range(start, end) + def to(limit: Int) = new Range(start, limit + 1) + } + + def matmul(xs: Array[Array[Double]], ys: Array[Array[Double]]): Array[Array[Double]] = { + def nrows = xs.length + def ncols = ys(0).length + def n = ys.length + assert(xs(0).length == n) + val zs = Array.ofDim[Double](nrows, ncols) + for (i <- intWrapper(0) until nrows) + for (j <- 0 until ncols) { + var x = 0.0 + for (k <- 0 until n) + x += xs(i)(k) * ys(k)(j) + zs(i)(j) = x + } + zs + } + + implicit class intArrayOps(arr: Array[Int]) { + transparent def foreach(op: => Int => Unit): Unit = { + var i = 0 + while (i < arr.length) { + op(arr(i)) + i += 1 + } + } + } + + def sum(ints: Array[Int]): Int = { + var t = 0 + for (n <- ints) t += n + t + } + + def main(args: Array[String]) = { + 1.until(10).foreach(i => println(i)) + 1.until(10).foreach(println(_)) + 1.until(10).foreach(println) + for (i <- 1 to 10) println(i) + + for (k1 <- 1 to 10) + for (k2 <- 1 to 10) + println(s"$k1") + + val xs = Array(1, 2, 3, 4) + assert(sum(xs) == 10, sum(xs)) + } +} diff --git a/tests/run/transparentPower.check b/tests/run/transparentPower.check new file mode 100644 index 000000000000..25e11563452f --- /dev/null +++ b/tests/run/transparentPower.check @@ -0,0 +1,2 @@ +1024.0 +2048.0 diff --git a/tests/run/transparentPower/Test_2.scala b/tests/run/transparentPower/Test_2.scala new file mode 100644 index 000000000000..8e16587b5653 --- /dev/null +++ b/tests/run/transparentPower/Test_2.scala @@ -0,0 +1,9 @@ +import p.pow.power +object Test { + + def main(args: Array[String]): Unit = { + println(power(2.0, 10)) + def x = 2.0 + println(power(x, 11)) + } +} diff --git a/tests/run/transparentPower/power_1.scala b/tests/run/transparentPower/power_1.scala new file mode 100644 index 000000000000..ef43488ce387 --- /dev/null +++ b/tests/run/transparentPower/power_1.scala @@ -0,0 +1,12 @@ +package p + +object pow { + + 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) + if (n % 2 == 0) y * y else y * y * x + } +} diff --git a/tests/run/transparentPrivates.scala b/tests/run/transparentPrivates.scala new file mode 100644 index 000000000000..8498203af9ba --- /dev/null +++ b/tests/run/transparentPrivates.scala @@ -0,0 +1,36 @@ +object Test { + + class C[T](private val x: T) { + + private def foo[Z](z: Z): T = x + + private var y: T = _ + + transparent def get1 = x + transparent def get2[U](c: C[U]) = c.x + + transparent def foo1(x: Int) = foo(x) + transparent def foo2[U](c: C[U]) = c.foo(x) + + 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 + transparent def get1 = x + } + + def main(args: Array[String]) = { + val cc = new C(2) + assert(cc.get1 == 2) + assert(cc.get2(cc) == 2) + assert(cc.foo1(1) == 2) + assert(cc.foo2(cc) == 2) + assert(cc.set1(3) == 3) + assert(cc.set2(cc) == 2) + + assert(CC.get1 == 3) + } + +} diff --git a/tests/run/transparentProtected.scala b/tests/run/transparentProtected.scala new file mode 100644 index 000000000000..478f599b2293 --- /dev/null +++ b/tests/run/transparentProtected.scala @@ -0,0 +1,22 @@ +package P { + class C { + protected def f(): Int = 22 + } +} + +package Q { + class D extends P.C { + class Inner { + transparent def g() = f() + } + } +} + +object Test extends App { + import Q._ + + val d = new D + val i = new d.Inner + val x = i.g() + assert(x == 22) +} From 89f3f93f24b8d0bb1412b2589901c28ebc67d0ae Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 4 Jun 2018 18:40:49 +0200 Subject: [PATCH 19/64] Pretype nested transparent applications --- .../src/dotty/tools/dotc/typer/Inliner.scala | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 1cc4c273bb09..79aada336484 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -613,7 +613,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case _ => tree }} - val inlineTyper = if (meth.isTransparentMethod) new InlineTyper else new InlineReTyper + val inlineTyper = if (meth.isTransparentMethod) new TransparentTyper else new InlineReTyper val inlineCtx = inlineContext(call).fresh.setTyper(inlineTyper).setNewScope // The complete translation maps references to `this` and parameters to @@ -641,7 +641,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { expansion } - trace(i"inlining $call\n, BINDINGS =\n${bindingsBuf.toList}%\n%\nEXPANSION =\n$expansion", inlining, show = true) { + trace(i"inlining $call", inlining, show = true) { // The final expansion runs a typing pass over the inlined tree. See InlineTyper for details. val expansion1 = inlineTyper.typed(expansion, pt)(inlineCtx) @@ -649,8 +649,10 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { /** All bindings in `bindingsBuf` except bindings of inlineable closures */ val bindings = bindingsBuf.toList.map(_.withPos(call.pos)) - inlining.println(i"original bindings = $bindings%\n%") - inlining.println(i"original expansion = $expansion1") + if (ctx.settings.verbose.value) { + inlining.println(i"original bindings = $bindings%\n%") + inlining.println(i"original expansion = $expansion1") + } val (finalBindings, finalExpansion) = dropUnusedDefs(bindings, expansion1) @@ -754,13 +756,24 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } /** A full typer used for transparent methods */ - private class InlineTyper extends Typer with InlineTyping { + private class TransparentTyper extends Typer with InlineTyping { override def typedTypedSplice(tree: untpd.TypedSplice)(implicit ctx: Context): Tree = tree.splice match { case InlineableArg(rhs) => inlining.println(i"inline arg $tree -> $rhs"); rhs case _ => super.typedTypedSplice(tree) } + + /** Pre-type any nested calls to transparent methods. Otherwise the declared result type + * of these methods can influence constraints + */ + override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context) = { + def typeTransparent(tree: untpd.Tree): untpd.Tree = + if (tree.symbol.isTransparentMethod) untpd.TypedSplice(typed(tree)) + else tree + val tree1 = tree.args.mapConserve(typeTransparent) + super.typedApply(untpd.cpy.Apply(tree)(tree.fun, tree1), pt) + } } /** A re-typer used for inlined methods */ From c70c8792a91bf6124d7dd3b3b7b14a955a5b6c9f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 4 Jun 2018 18:41:57 +0200 Subject: [PATCH 20/64] Pretype nested transparent applications This lets us way HList concat in the most trivial way possible. --- tests/run/typelevel.scala | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/run/typelevel.scala b/tests/run/typelevel.scala index 737943b59753..f428e7201363 100644 --- a/tests/run/typelevel.scala +++ b/tests/run/typelevel.scala @@ -28,4 +28,39 @@ object Test extends App { println(x0) println(x1) println(x2) + + trait HList { + def isEmpty: Boolean + def head: Any + def tail: HList + } + + class HNil extends HList { + transparent override def isEmpty = true + override def head: Nothing = ??? + override def tail: Nothing = ??? + } + + case object HNil extends HNil + + case class HCons[H, T <: HList](hd: H, tl: T) extends HList { + transparent override def isEmpty = false + override def head: H = this.hd + override def tail: T = this.tl + } + + transparent def concat(xs: HList, ys: HList): HList = + if (xs.isEmpty) ys + else HCons(xs.head, concat(xs.tail, ys)) + + transparent 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(1, HCons("a", HNil))) } \ No newline at end of file From 51139f0eecbbcc9965e9443801ded9c965b2d612 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 4 Jun 2018 21:05:33 +0200 Subject: [PATCH 21/64] Fix wrong owner when reading annotations. This was the enclosing class instead of the method containing the annotation. Fixing this is surprisingly hard. --- .../tools/dotc/core/tasty/TreeUnpickler.scala | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index aa206e3e173d..621fc344fb83 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -516,7 +516,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, readAnnot, readWithin, NoSymbol) pickling.println(i"creating symbol $name at $start with flags $givenFlags") val flags = normalizeFlags(tag, givenFlags, name, isAbsType, rhsIsEmpty) def adjustIfModule(completer: LazyType) = @@ -564,10 +564,10 @@ class TreeUnpickler(reader: TastyReader, * boundary symbol. */ def readModifiers[WithinType, AnnotType] - (end: Addr, readAnnot: Context => AnnotType, readWithin: Context => WithinType, defaultWithin: WithinType) - (implicit ctx: Context): (FlagSet, List[AnnotType], WithinType) = { + (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 annotFns: List[Symbol => AnnotType] = Nil var privateWithin = defaultWithin while (currentAddr.index != end.index) { def addFlag(flag: FlagSet) = { @@ -636,12 +636,14 @@ class TreeUnpickler(reader: TastyReader, val end = readEnd() val tp = readType() val lazyAnnotTree = readLaterWithOwner(end, rdr => ctx => rdr.readTerm()(ctx)) - if (tp.isRef(defn.BodyAnnot)) - LazyBodyAnnotation(implicit ctx => lazyAnnotTree(owner).complete) - else - Annotation.deferredSymAndTree( - implicit ctx => tp.typeSymbol, - implicit ctx => lazyAnnotTree(owner).complete) + + owner => + 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 @@ -1287,7 +1289,7 @@ class TreeUnpickler(reader: TastyReader, def readMods(): untpd.Modifiers = { val (flags, annots, privateWithin) = readModifiers(end, readUntypedAnnot, readUntypedWithin, EmptyTypeName) - untpd.Modifiers(flags, privateWithin, annots) + untpd.Modifiers(flags, privateWithin, annots.map(_(NoSymbol))) } def readRhs(): untpd.Tree = @@ -1406,8 +1408,8 @@ class TreeUnpickler(reader: TastyReader, private val readUntypedWithin: Context => TypeName = implicit ctx => readName().toTypeName - private val readUntypedAnnot: Context => untpd.Tree = - implicit ctx => readUntyped() + private val readUntypedAnnot: Context => Symbol => untpd.Tree = + implicit ctx => _ => readUntyped() // ------ Setting positions ------------------------------------------------ From 8d7936e79c1bc9a0625b48574b200cb124d5b3dd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 4 Jun 2018 22:39:42 +0200 Subject: [PATCH 22/64] Expand test --- tests/run/typelevel.scala | 55 ++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/tests/run/typelevel.scala b/tests/run/typelevel.scala index f428e7201363..d5eebf4665f6 100644 --- a/tests/run/typelevel.scala +++ b/tests/run/typelevel.scala @@ -1,10 +1,17 @@ object Test extends App { - trait Nat + trait Nat { + def toInt: Int + } + + case object Z extends Nat { + transparent def toInt = 0 + } - case object Z extends Nat type Z = Z.type - case class S[N <: Nat](n: Nat) extends Nat + case class S[N <: Nat](n: N) extends Nat { + transparent def toInt = n.toInt + 1 + } abstract class HasResult[T] { type Result = T } case class ToNat[+T](val value: T) extends HasResult[T] @@ -13,7 +20,7 @@ object Test extends App { 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) @@ -28,6 +35,10 @@ object Test extends App { println(x0) println(x1) println(x2) + transparent val i0 = y0.toInt + val j0: 0 = i0 + transparent val i2 = y2.toInt + val j2: 2 = i2 trait HList { def isEmpty: Boolean @@ -41,7 +52,7 @@ object Test extends App { override def tail: Nothing = ??? } - case object HNil extends HNil + lazy val HNil: HNil = new HNil case class HCons[H, T <: HList](hd: H, tl: T) extends HList { transparent override def isEmpty = false @@ -61,6 +72,38 @@ object Test extends App { val r3 = concat(xs, xs) val r4 = concat(HNil, HCons(1, HCons("a", HNil))) - val r5 = concat(HCons(1, HCons("a", HNil)), HNil) + val r5 = concat(HCons(1, HCons("a", HNil)) , HNil) val r6 = concat(HCons(1, HCons("a", HNil)), HCons(1, HCons("a", HNil))) + + transparent def size(xs: HList): Nat = + if (xs.isEmpty) Z + else S(size(xs.tail)) + + val s0 = size(HNil) + val s1 = size(xs) + + transparent def index(xs: HList, inline idx: Int): Any = + if (idx == 0) 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 + +/** Does not work yet: + + implicit class HListDeco(xs: HList) { + transparent def ++ (ys: HList) = concat(xs, ys) + } + + val rr0 = 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 + +*/ } \ No newline at end of file From 9e093c8cfe634d3cf9fcf36076d7a615c9fa84e3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 5 Jun 2018 13:19:34 +0200 Subject: [PATCH 23/64] More tests --- tests/run/typelevel.scala | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/tests/run/typelevel.scala b/tests/run/typelevel.scala index d5eebf4665f6..52d29ad7edc2 100644 --- a/tests/run/typelevel.scala +++ b/tests/run/typelevel.scala @@ -41,13 +41,14 @@ object Test extends App { val j2: 2 = i2 trait HList { - def isEmpty: Boolean + transparent def isEmpty: Boolean = length == 0 + def length: Int def head: Any def tail: HList } class HNil extends HList { - transparent override def isEmpty = true + transparent override def length = 0 override def head: Nothing = ??? override def tail: Nothing = ??? } @@ -55,7 +56,7 @@ object Test extends App { lazy val HNil: HNil = new HNil case class HCons[H, T <: HList](hd: H, tl: T) extends HList { - transparent override def isEmpty = false + transparent override def length = 1 + tl.length override def head: H = this.hd override def tail: T = this.tl } @@ -79,8 +80,28 @@ object Test extends App { if (xs.isEmpty) Z else S(size(xs.tail)) + transparent def sizeDefensive(xs: HList): Nat = xs.isEmpty match { + case true => Z + case false => S(sizeDefensive(xs.tail)) + } +/* + transparent def toInt1[T]: Nat = type T match { + case Z => 0 + case S[type N] => toInt[N] + 1 + } + + transparent def toInt1[T]: Nat = implicit match { + case T <:< Z => 0 + case T <:< S[type N] => toInt[N] + 1 + } +*/ + 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, inline idx: Int): Any = if (idx == 0) xs.head @@ -92,10 +113,12 @@ object Test extends App { var ss3: String = s3 def s4 = index(xs, 2) def ss4: Nothing = s4 + val s5 = index(xs, xs.length - 1) + val ss5: String = s5 /** Does not work yet: - implicit class HListDeco(xs: HList) { + implicit class HListDeco(transparent val xs: HList) { transparent def ++ (ys: HList) = concat(xs, ys) } From c6616fd913ed07cf53cde9d8b2ce376f194ebd7a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 15 Jun 2018 10:59:32 -0700 Subject: [PATCH 24/64] Fix rebase breakage --- .../src/dotty/tools/dotc/typer/Inliner.scala | 2 +- tests/run/typelevel.scala | 105 +++++++++--------- 2 files changed, 55 insertions(+), 52 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 79aada336484..2692231c13bc 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -341,7 +341,7 @@ object Inliner { for (iref <- referenced.implicitRefs.toList) yield { val localImplicit = iref.symbol.asTerm.copy( owner = inlineMethod, - name = UniqueName.fresh(iref.symbol.name.asTermName), + name = UniqueInlineName.fresh(iref.symbol.name.asTermName), flags = Implicit | Method | Stable, info = iref.symbol.info.ensureMethodic, coord = inlineMethod.pos).asTerm diff --git a/tests/run/typelevel.scala b/tests/run/typelevel.scala index 52d29ad7edc2..b88c562010cb 100644 --- a/tests/run/typelevel.scala +++ b/tests/run/typelevel.scala @@ -1,20 +1,45 @@ -object Test extends App { - - trait Nat { - def toInt: Int - } - case object Z extends Nat { - transparent def toInt = 0 - } +trait Nat { + def toInt: Int +} + +case object Z extends Nat { + transparent def toInt = 0 +} + +case class S[N <: Nat](n: N) extends Nat { + transparent def toInt = n.toInt + 1 +} + +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 +} + +case class ToNat[T](val value: T) { + type Result = T +} +object Test extends App { + type HNil = HNil.type type Z = Z.type - case class S[N <: Nat](n: N) extends Nat { - transparent def toInt = n.toInt + 1 - } - - abstract class HasResult[T] { type Result = T } - case class ToNat[+T](val value: T) extends HasResult[T] transparent def ToNat(inline n: Int): ToNat[Nat] = if n == 0 then new ToNat(Z) @@ -40,32 +65,11 @@ object Test extends App { transparent val i2 = y2.toInt val j2: 2 = i2 - trait HList { - transparent def isEmpty: Boolean = length == 0 - def length: Int - def head: Any - def tail: HList - } - - class HNil extends HList { - transparent override def length = 0 - override def head: Nothing = ??? - override def tail: Nothing = ??? - } - - lazy val HNil: HNil = new HNil - - case class HCons[H, T <: HList](hd: H, tl: T) extends HList { - transparent override def length = 1 + tl.length - override def head: H = this.hd - override def tail: T = this.tl - } - transparent def concat(xs: HList, ys: HList): HList = - if (xs.isEmpty) ys + if xs.isEmpty then ys else HCons(xs.head, concat(xs.tail, ys)) - transparent val xs = HCons(1, HCons("a", HNil)) + val xs = HCons(1, HCons("a", HNil)) val r0 = concat(HNil, HNil) val r1 = concat(HNil, xs) @@ -74,27 +78,16 @@ object Test extends App { 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(1, HCons("a", HNil))) + val r6 = concat(HCons(1, HCons("a", HNil)), HCons(true, HCons(1.0, HNil))) transparent def size(xs: HList): Nat = - if (xs.isEmpty) Z + 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)) } -/* - transparent def toInt1[T]: Nat = type T match { - case Z => 0 - case S[type N] => toInt[N] + 1 - } - - transparent def toInt1[T]: Nat = implicit match { - case T <:< Z => 0 - case T <:< S[type N] => toInt[N] + 1 - } -*/ val s0 = size(HNil) val s1 = size(xs) @@ -104,7 +97,7 @@ object Test extends App { val l1a: 2 = l1 transparent def index(xs: HList, inline idx: Int): Any = - if (idx == 0) xs.head + if idx == 0 then xs.head else index(xs.tail, idx - 1) val s2 = index(xs, 0) @@ -115,7 +108,17 @@ object Test extends App { def ss4: Nothing = s4 val s5 = index(xs, xs.length - 1) val ss5: String = s5 +/* + 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 => 0 + case T <:< S[type N] => toInt[N] + 1 + } +*/ /** Does not work yet: implicit class HListDeco(transparent val xs: HList) { From fdbf0839eec5e83ff337eae85a7a94637016ed90 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 16 Jun 2018 07:57:15 -0700 Subject: [PATCH 25/64] Better error position when inline call limit exceeded It used to be the position of the last inline call, which is not very helpful. Now it is the position of the first inline call, which started the recursive inline expansion. --- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 2692231c13bc..f79be1fa161e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -381,7 +381,8 @@ object Inliner { 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.""" + |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 */ From a9e9b2908ebe4eb543df50b1b84756dc313bde85 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 16 Jun 2018 11:24:48 -0700 Subject: [PATCH 26/64] Recursive transparent calls inline only in redex position Suppress recursive inlining of transparent methods unless the call is in redex position. Recursive calls to transparent methods under an if-then-else or match are not inlined unless the if-then-else or match is reduced away. Recursive calls on the rhs of a method definition, type definition, or lazy value definition are also not inlined. In principle, we should also suppress inlining calls in a by-name parameter position, but this is harder to do, first because we don't have always have the function type when deciding this (in typedApply of TransparentTyper), and second, because if the outer call with the by-name parameter is to a transparent method, we might decide after inlining that call that the by-name parameter is after all in redex position, so we should inline it recursively. But by that time, we might already have used the recusive call's type to instantiate type parameters of the enclosing function. If the recursive call is not inlined beforehand, these instantiations might lose precision. Therefore, it's safer to inline transparent calls in by-name parameters anyway. --- compiler/src/dotty/tools/dotc/core/Mode.scala | 9 ++-- .../src/dotty/tools/dotc/typer/Inliner.scala | 47 +++++++++++++++---- .../src/dotty/tools/dotc/typer/Typer.scala | 14 +++--- 3 files changed, 51 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index b7f47122d827..fb5ace7a798b 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -94,9 +94,12 @@ 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") + val TransparentBody = newMode(22, "TransparentBody") - /** Read comments from definitions when unpickling from TASTY */ - val ReadComments = newMode(22, "ReadComments") + /** Suppress inlining transparent method applications */ + val NoInlineTransparent = newMode(23, "NoInlineTransparent") } diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index f79be1fa161e..e450abe5e0ca 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -632,7 +632,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { // 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 + // it seems non-trivial to come up with rules that work in all cases. inlineCtx.enter(idef.symbol) } expansion @@ -691,6 +691,17 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { */ trait InlineTyping extends Typer { + protected def tryInline(tree: tpd.Tree)(implicit ctx: Context) = tree match { + case InlineableArg(rhs) => + inlining.println(i"inline arg $tree -> $rhs") + rhs + case _ => + EmptyTree + } + + /** The context to be used for sub-expressions that are not in redex position. */ + protected def noRedexCtx(implicit ctx: Context): Context + override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = { tpe match { case tpe @ TypeRef(pre, _) if !tpe.symbol.isAccessibleFrom(pre, superAccess) => @@ -713,7 +724,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { else Block(cond1 :: Nil, selected) case _ => val if1 = untpd.cpy.If(tree)(cond = untpd.TypedSplice(cond1)) - super.typedIf(if1, pt) + super.typedIf(if1, pt)(noRedexCtx) } } @@ -759,11 +770,11 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { /** A full typer used for transparent methods */ private class TransparentTyper extends Typer with InlineTyping { + /** Transparent methods don't inline recursively unless in redex position */ + def noRedexCtx(implicit ctx: Context) = ctx.addMode(Mode.NoInlineTransparent) + override def typedTypedSplice(tree: untpd.TypedSplice)(implicit ctx: Context): Tree = - tree.splice match { - case InlineableArg(rhs) => inlining.println(i"inline arg $tree -> $rhs"); rhs - case _ => super.typedTypedSplice(tree) - } + tryInline(tree.splice) `orElse` super.typedTypedSplice(tree) /** Pre-type any nested calls to transparent methods. Otherwise the declared result type * of these methods can influence constraints @@ -775,16 +786,32 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { val tree1 = tree.args.mapConserve(typeTransparent) super.typedApply(untpd.cpy.Apply(tree)(tree.fun, tree1), pt) } + + override def typedValDef(tree: untpd.ValDef, sym: Symbol)(implicit ctx: Context) = { + import untpd.modsDeco + super.typedValDef(tree, sym)(if (tree.mods.is(Lazy)) noRedexCtx else ctx) + } + + override def typedDefDef(tree: untpd.DefDef, sym: Symbol)(implicit ctx: Context) = + super.typedDefDef(tree, sym)(noRedexCtx) + + override def typedTypeDef(tree: untpd.TypeDef, sym: Symbol)(implicit ctx: Context) = + super.typedTypeDef(tree, sym)(noRedexCtx) + + override def typedClassDef(tree: untpd.TypeDef, sym: ClassSymbol)(implicit ctx: Context) = + super.typedClassDef(tree, sym)(noRedexCtx) } /** A re-typer used for inlined methods */ private class InlineReTyper extends ReTyper with InlineTyping { + /** Inline methods always expand all recursive inline calls, whether in redex + * position or not. + */ + def noRedexCtx(implicit ctx: Context) = ctx + 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) - } + tryInline(tree.asInstanceOf[tpd.Tree]) `orElse` super.typedIdent(tree, pt) override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { assert(tree.hasType, tree) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 38acb3b4f537..cb94f1b0eb98 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2356,12 +2356,14 @@ class Typer extends Namer 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)) + def suppressInline = + ctx.owner.ownersIterator.exists(_.isInlineableMethod) || + tree.symbol.isTransparentMethod && ctx.mode.is(Mode.NoInlineTransparent) || + ctx.settings.YnoInline.value || + ctx.isAfterTyper || + ctx.reporter.hasErrors + if (Inliner.hasBodyToInline(tree.symbol) && !suppressInline) + readaptSimplified(Inliner.inlineCall(tree, pt)) else 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, From add85eaa842ef1cf109c64452920dfd65acaa576 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 16 Jun 2018 17:06:02 -0700 Subject: [PATCH 27/64] Fix test Adjust error position to changes --- tests/neg/power.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/neg/power.scala b/tests/neg/power.scala index 6230b4e51a21..b57812b957e3 100644 --- a/tests/neg/power.scala +++ b/tests/neg/power.scala @@ -5,11 +5,11 @@ object Test { 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 } } From d4d66249ce3bda563badf6ac09855dc961a93ebc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 18 Jun 2018 10:12:54 -0700 Subject: [PATCH 28/64] Reduce projections of data type fields in inliner Reduce terms equivalent to `new C(args).x` to `arg_i` if `x` refers to parameter `i` of class `C` and `C`'s constructor does not have side effects. --- .../src/dotty/tools/dotc/typer/Inliner.scala | 90 +++++++++++++++++-- .../src/dotty/tools/dotc/typer/Typer.scala | 1 - tests/run/typelevel.scala | 26 +++--- 3 files changed, 97 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index e450abe5e0ca..7663657eca35 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -41,6 +41,15 @@ object Inliner { def markContextualImplicit(tree: Tree)(implicit ctx: Context): Unit = methPart(tree).putAttachment(ContextualImplicit, ()) + /** A key to be used in a context property that provides a map from enclosing implicit + * value bindings to their right hand sides. + */ + private val InlineBindings = new Property.Key[MutableSymbolMap[Tree]] + + /** 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 + class InlineAccessors extends AccessProxies { /** If an inline accessor name wraps a unique inline name, this is taken as indication @@ -285,7 +294,7 @@ object Inliner { case _: Ident | _: This => //println(i"leaf: $tree at ${tree.pos}") if (isExternal(tree.symbol)) { - inlining.println(i"type at pos ${tree.pos.toSynthetic} = ${tree.tpe}") + if (ctx.debug) inlining.println(i"type at pos ${tree.pos.toSynthetic} = ${tree.tpe}") typeAtPos(tree.pos.toSynthetic) = tree.tpe } case _: Select if tree.symbol.name.is(InlineAccessorName) => @@ -375,7 +384,13 @@ object Inliner { def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = 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) + 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) + } } else errorTree( tree, @@ -642,14 +657,35 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { expansion } + /** 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. + * Also, set position to the one of the inline call. + */ + def normalizeBinding(binding: ValOrDefDef)(implicit ctx: Context) = { + val binding1 = binding match { + case binding: ValDef => + val rhs1 = reduceProjection(binding.rhs) + inlineBindings(inlineCtx).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) + } + trace(i"inlining $call", inlining, show = true) { + /** All bindings in `bindingsBuf` */ + val bindings = bindingsBuf.toList.map(normalizeBinding) + // The final expansion runs a typing pass over the inlined tree. See InlineTyper for details. val expansion1 = inlineTyper.typed(expansion, pt)(inlineCtx) - /** All bindings in `bindingsBuf` except bindings of inlineable closures */ - val bindings = bindingsBuf.toList.map(_.withPos(call.pos)) - if (ctx.settings.verbose.value) { inlining.println(i"original bindings = $bindings%\n%") inlining.println(i"original expansion = $expansion1") @@ -661,6 +697,45 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } } + /** An extractor for terms equivalent to `new C(args)`, returning the class `C` + * 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. + */ + object NewInstance { + def unapply(tree: Tree)(implicit ctx: Context): Option[(Symbol, List[Tree])] = tree match { + case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) => + Some((tpt.tpe.classSymbol, args)) + case Ident(_) => + inlineBindings.get(tree.symbol).flatMap(unapply) + case Inlined(_, _, expansion) => + unapply(expansion) + case Block(stats, expr) if isPureExpr(tree) => + unapply(expr) + case _ => + None + } + } + + /** 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, otherwise `tree` itself. + */ + def reduceProjection(tree: Tree)(implicit ctx: Context): Tree = { + if (meth.isTransparentMethod) { + if (ctx.debug) inlining.println(i"try reduce projection $tree") + tree match { + case Select(NewInstance(cls, args), field) if cls.is(NoInits) => + val idx = cls.asClass.paramAccessors.indexOf(tree.symbol) + if (idx >= 0 && idx < args.length) { + inlining.println(i"projecting $tree -> ${args(idx)}") + return args(idx) + } + case _ => + } + } + tree + } + /** An extractor for references to inlineable arguments. These are : * - by-value arguments marked with `inline` * - all by-name arguments @@ -774,7 +849,10 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { def noRedexCtx(implicit ctx: Context) = ctx.addMode(Mode.NoInlineTransparent) override def typedTypedSplice(tree: untpd.TypedSplice)(implicit ctx: Context): Tree = - tryInline(tree.splice) `orElse` super.typedTypedSplice(tree) + reduceProjection(tryInline(tree.splice) `orElse` super.typedTypedSplice(tree)) + + override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context) = + reduceProjection(super.typedSelect(tree, pt)) /** Pre-type any nested calls to transparent methods. Otherwise the declared result type * of these methods can influence constraints diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index cb94f1b0eb98..9f9cc603c76c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2231,7 +2231,6 @@ class Typer extends Namer } val args = eraseErasedArgs(implicitArgs(wtp.paramInfos)) - def propagatedFailure(args: List[Tree]): Type = args match { case arg :: args1 => arg.tpe match { diff --git a/tests/run/typelevel.scala b/tests/run/typelevel.scala index b88c562010cb..172bc6ccf974 100644 --- a/tests/run/typelevel.scala +++ b/tests/run/typelevel.scala @@ -108,6 +108,19 @@ object Test extends App { def ss4: Nothing = s4 val s5 = index(xs, xs.length - 1) val ss5: String = s5 + + class HListDeco(val as: HList) extends AnyVal { + transparent def ++ (bs: HList) = concat(as, bs) + } + + 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 toInt1[T]: Int = type T match { case Z => 0 @@ -119,17 +132,4 @@ object Test extends App { case T <:< S[type N] => toInt[N] + 1 } */ -/** Does not work yet: - - implicit class HListDeco(transparent val xs: HList) { - transparent def ++ (ys: HList) = concat(xs, ys) - } - - val rr0 = 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 - -*/ } \ No newline at end of file From 812890286a6d624235611d10434b12c76038357b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 18 Jun 2018 13:43:24 -0700 Subject: [PATCH 29/64] Check baseclasses when determining purity of class A class is pure for the purpose of reducing projections in inlinig if none of its baseclasses has an initializer. To make this robust wrt compilation order, we need to move computation of NoInits flags from Typer to the class completer. --- .../src/dotty/tools/dotc/typer/Inliner.scala | 2 +- tests/run/tl.scala | 56 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 tests/run/tl.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 7663657eca35..815caab5ad62 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -724,7 +724,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { if (meth.isTransparentMethod) { if (ctx.debug) inlining.println(i"try reduce projection $tree") tree match { - case Select(NewInstance(cls, args), field) if cls.is(NoInits) => + case Select(NewInstance(cls, args), field) if cls.isNoInitsClass => val idx = cls.asClass.paramAccessors.indexOf(tree.symbol) if (idx >= 0 && idx < args.length) { inlining.println(i"projecting $tree -> ${args(idx)}") diff --git a/tests/run/tl.scala b/tests/run/tl.scala new file mode 100644 index 000000000000..0b95302d3701 --- /dev/null +++ b/tests/run/tl.scala @@ -0,0 +1,56 @@ +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(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) + } + + 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 + 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 From 244250e19ea6e9da34f58a8add2e2beb92f35d9e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 18 Jun 2018 18:19:09 -0700 Subject: [PATCH 30/64] Replace inlined pure constant expressions by literals Since the expression is inlined, we will never need its constituents for hyperlinking. So we don't lose editing functionality by replacing the epxression with a literal. Because of other reductions we might end up with large constant expressions that are worth collapsing. --- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 4 ++-- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 815caab5ad62..cd2646f13fd9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -838,7 +838,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case _ => tree } - betaReduce(super.typedApply(tree, pt)) + constToLiteral(betaReduce(super.typedApply(tree, pt))) } } @@ -852,7 +852,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { reduceProjection(tryInline(tree.splice) `orElse` super.typedTypedSplice(tree)) override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context) = - reduceProjection(super.typedSelect(tree, pt)) + constToLiteral(reduceProjection(super.typedSelect(tree, pt))) /** Pre-type any nested calls to transparent methods. Otherwise the declared result type * of these methods can influence constraints diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 9f9cc603c76c..e8b6b1f86533 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2101,7 +2101,7 @@ class Typer extends Namer 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) } From 86517aff463ad45665257746af8fc2394bf39b0f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 18 Jun 2018 18:53:25 -0700 Subject: [PATCH 31/64] Add neg test --- tests/neg/typelevel.scala | 58 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/neg/typelevel.scala diff --git a/tests/neg/typelevel.scala b/tests/neg/typelevel.scala new file mode 100644 index 000000000000..bccfb77f254a --- /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(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 + 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 From a736ea9c8cff84bd8b5c7e2c91e4de0a4ff5f11b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 20 Jun 2018 16:06:46 -0400 Subject: [PATCH 32/64] Allow to project on private fields Need to understand inline accessors in order to do it. --- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index cd2646f13fd9..259e907f4b6e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -725,7 +725,16 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { if (ctx.debug) inlining.println(i"try reduce projection $tree") tree match { case Select(NewInstance(cls, args), field) if cls.isNoInitsClass => - val idx = cls.asClass.paramAccessors.indexOf(tree.symbol) + def matches(param: Symbol, selection: Symbol): Boolean = + param == selection || { + selection.name match { + case InlineAccessorName(underlying) => + param.name == underlying && selection.info.isInstanceOf[ExprType] + case _ => + false + } + } + val idx = cls.asClass.paramAccessors.indexWhere(matches(_, tree.symbol)) if (idx >= 0 && idx < args.length) { inlining.println(i"projecting $tree -> ${args(idx)}") return args(idx) From 8e5c8b480707e58372bf1a180113190b53687e78 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 20 Jun 2018 16:07:26 -0400 Subject: [PATCH 33/64] Don't forget bindings in projections. Plus new test cases --- .../src/dotty/tools/dotc/typer/Inliner.scala | 32 +++++++----- tests/neg/typelevel.scala | 2 +- tests/run/typelevel.scala | 12 +++-- tests/run/typelevel1.scala | 52 +++++++++++++++++++ 4 files changed, 80 insertions(+), 18 deletions(-) create mode 100644 tests/run/typelevel1.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 259e907f4b6e..a7207afef393 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -702,17 +702,23 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { * follow a reference to an inline value binding to its right hand side. */ object NewInstance { - def unapply(tree: Tree)(implicit ctx: Context): Option[(Symbol, List[Tree])] = tree match { - case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) => - Some((tpt.tpe.classSymbol, args)) - case Ident(_) => - inlineBindings.get(tree.symbol).flatMap(unapply) - case Inlined(_, _, expansion) => - unapply(expansion) - case Block(stats, expr) if isPureExpr(tree) => - unapply(expr) - case _ => - None + 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(Select(New(tpt), nme.CONSTRUCTOR), args) => + Some((tpt.tpe.classSymbol, args, Nil)) + 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 + } } } @@ -724,7 +730,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { if (meth.isTransparentMethod) { if (ctx.debug) inlining.println(i"try reduce projection $tree") tree match { - case Select(NewInstance(cls, args), field) if cls.isNoInitsClass => + case Select(NewInstance(cls, args, prefix), field) if cls.isNoInitsClass => def matches(param: Symbol, selection: Symbol): Boolean = param == selection || { selection.name match { @@ -737,7 +743,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { val idx = cls.asClass.paramAccessors.indexWhere(matches(_, tree.symbol)) if (idx >= 0 && idx < args.length) { inlining.println(i"projecting $tree -> ${args(idx)}") - return args(idx) + return seq(prefix, args(idx)) } case _ => } diff --git a/tests/neg/typelevel.scala b/tests/neg/typelevel.scala index bccfb77f254a..f75c425a4e9d 100644 --- a/tests/neg/typelevel.scala +++ b/tests/neg/typelevel.scala @@ -25,7 +25,7 @@ object Test { if xs.isEmpty then ys else HCons(xs.head, concat(xs.tail, ys)) - class Deco(val as: HList) { + class Deco(private val as: HList) { transparent def ++ (bs: HList) = concat(as, bs) } diff --git a/tests/run/typelevel.scala b/tests/run/typelevel.scala index 172bc6ccf974..7181301d75cf 100644 --- a/tests/run/typelevel.scala +++ b/tests/run/typelevel.scala @@ -27,7 +27,8 @@ case object HNil extends HList { } // (H, T) -case class HCons[H, T <: HList](hd: H, tl: T) extends HList { +@annotation.showAsInfix(true) +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 @@ -65,6 +66,10 @@ object Test extends App { 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)) @@ -109,9 +114,8 @@ object Test extends App { val s5 = index(xs, xs.length - 1) val ss5: String = s5 - class HListDeco(val as: HList) extends AnyVal { - transparent def ++ (bs: HList) = concat(as, bs) - } + + //val ys = 1 :: "a" :: HNil transparent implicit def hlistDeco(xs: HList): HListDeco = new HListDeco(xs) diff --git a/tests/run/typelevel1.scala b/tests/run/typelevel1.scala new file mode 100644 index 000000000000..3a490b596384 --- /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 def length = 0 + def head: Nothing = ??? + def tail: Nothing = ??? +} + +case class :: [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 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 From f5a3b2e705c9f3448f6565e8e9af3517cbbde56d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 21 Jun 2018 11:09:59 -0400 Subject: [PATCH 34/64] Inline transparent methods even if result type does not match Inlining transparent methods can improve the result type, so we should do this anyway even if the original result type does not match. --- .../src/dotty/tools/dotc/typer/Typer.scala | 22 ++++++------------- tests/run/typelevel.scala | 10 ++++++++- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index e8b6b1f86533..34bccbec2026 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2093,12 +2093,10 @@ 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) } final def adapt(tree: Tree, pt: Type)(implicit ctx: Context): Tree = { @@ -2338,7 +2336,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) && @@ -2352,18 +2350,12 @@ class Typer extends Namer checkEqualityEvidence(tree, pt) tree } + else if (Inliner.isInlineable(tree.symbol)) + readaptSimplified(Inliner.inlineCall(tree, pt)) else if (tree.tpe <:< pt) { if (pt.hasAnnotation(defn.InlineParamAnnot)) checkInlineConformant(tree, isFinal = false, "argument to inline parameter") - def suppressInline = - ctx.owner.ownersIterator.exists(_.isInlineableMethod) || - tree.symbol.isTransparentMethod && ctx.mode.is(Mode.NoInlineTransparent) || - ctx.settings.YnoInline.value || - ctx.isAfterTyper || - ctx.reporter.hasErrors - if (Inliner.hasBodyToInline(tree.symbol) && !suppressInline) - readaptSimplified(Inliner.inlineCall(tree, pt)) - else if (ctx.typeComparer.GADTused && pt.isValueType) + 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 diff --git a/tests/run/typelevel.scala b/tests/run/typelevel.scala index 7181301d75cf..a137b554a91a 100644 --- a/tests/run/typelevel.scala +++ b/tests/run/typelevel.scala @@ -125,6 +125,14 @@ object Test extends App { 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 toInt1[T]: Int = type T match { case Z => 0 @@ -132,7 +140,7 @@ object Test extends App { } transparent def toInt1[T]: Nat = implicit match { - case C[type T, type U], T =:= U => 0 + case C[type T, type U], T =:= U => case T <:< S[type N] => toInt[N] + 1 } */ From fe8870eb144e90cf4baccc8aef8945adbe1cd3e9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 21 Jun 2018 11:17:15 -0400 Subject: [PATCH 35/64] Don't constrain result type of inlineable transparent methods We need to inline the method in this case before constraining the result type. This more systematic solution eliminates an annoying special case for typing Apply nodes in the inliner. --- .../dotty/tools/dotc/typer/Applications.scala | 18 ++++++------ .../dotty/tools/dotc/typer/Inferencing.scala | 2 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 28 +++++++++++-------- .../dotty/tools/dotc/typer/ProtoTypes.scala | 6 ++++ .../src/dotty/tools/dotc/typer/Typer.scala | 5 ++-- 5 files changed, 36 insertions(+), 23 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 11ed46942e08..ccec09ce8efa 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 */ @@ -1280,12 +1280,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 +1304,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 diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 9b6c128d023b..b0218c03d766 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -408,7 +408,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 a7207afef393..91063f7f5f0c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -373,6 +373,23 @@ object Inliner { def bodyToInline(sym: SymDenotation)(implicit ctx: Context): Tree = sym.unforcedAnnotation(defn.BodyAnnot).get.tree + /** Should call with method `meth` be inlined in this context? */ + def isInlineable(meth: Symbol)(implicit ctx: Context): Boolean = { + + def suppressInline = + ctx.owner.ownersIterator.exists(_.isInlineableMethod) || + meth.isTransparentMethod && ctx.mode.is(Mode.NoInlineTransparent) || + ctx.settings.YnoInline.value || + ctx.isAfterTyper || + ctx.reporter.hasErrors + + hasBodyToInline(meth.symbol) && !suppressInline + } + + /** Is `meth` a transparent method that should be inlined in this context? */ + def isTransparentInlineable(meth: Symbol)(implicit ctx: Context): Boolean = + meth.isTransparentMethod && isInlineable(meth) + /** Try to inline a call to a `@inline` method. Fail with error if the maximal * inline depth is exceeded. * @@ -869,17 +886,6 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context) = constToLiteral(reduceProjection(super.typedSelect(tree, pt))) - /** Pre-type any nested calls to transparent methods. Otherwise the declared result type - * of these methods can influence constraints - */ - override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context) = { - def typeTransparent(tree: untpd.Tree): untpd.Tree = - if (tree.symbol.isTransparentMethod) untpd.TypedSplice(typed(tree)) - else tree - val tree1 = tree.args.mapConserve(typeTransparent) - super.typedApply(untpd.cpy.Apply(tree)(tree.fun, tree1), pt) - } - override def typedValDef(tree: untpd.ValDef, sym: Symbol)(implicit ctx: Context) = { import untpd.modsDeco super.typedValDef(tree, sym)(if (tree.mods.is(Lazy)) noRedexCtx else ctx) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 5a32fcf19364..f38fb6db6e37 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -71,6 +71,12 @@ object ProtoTypes { if (!res) ctx.typerState.resetConstraintTo(savedConstraint) res } + + /** Constrain result unless `meth` is a transparent method in an inlineable context. + * In the latter case we should inline before constraining the result. + */ + def constrainResult(meth: Symbol, mt: Type, pt: Type)(implicit ctx: Context): Boolean = + Inliner.isTransparentInlineable(meth) || constrainResult(mt, pt) } object NoViewsAllowed extends Compatibility { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 34bccbec2026..28b59512de73 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2350,7 +2350,8 @@ class Typer extends Namer checkEqualityEvidence(tree, pt) tree } - else if (Inliner.isInlineable(tree.symbol)) + else if (Inliner.isInlineable(tree.symbol) && + (tree.symbol.isTransparentMethod || tree.tpe <:< pt)) readaptSimplified(Inliner.inlineCall(tree, pt)) else if (tree.tpe <:< pt) { if (pt.hasAnnotation(defn.InlineParamAnnot)) @@ -2387,7 +2388,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)) From c36911e4e99ab0a015da4f2dc3f51e40f924322a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 21 Jun 2018 11:31:18 -0400 Subject: [PATCH 36/64] Remove test This was was added by mistake. The correct test is neg/typelevel.scala --- tests/run/tl.scala | 56 ---------------------------------------------- 1 file changed, 56 deletions(-) delete mode 100644 tests/run/tl.scala diff --git a/tests/run/tl.scala b/tests/run/tl.scala deleted file mode 100644 index 0b95302d3701..000000000000 --- a/tests/run/tl.scala +++ /dev/null @@ -1,56 +0,0 @@ -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(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) - } - - 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 - 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 From 809ab6e9a46db39b53b10e254828d45aeef9fd77 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 23 Jun 2018 08:00:36 -0400 Subject: [PATCH 37/64] Take parents of class into account when determining NoInits flags We declare a class (not a trait) impure if it passes arguments to its parents. This is a very conservative estimate. It's hard to do better, though, since parent expressions are not always typed when we need to decide class purity. So we cannot use `isPureExpr` on argument expressions to find out more. --- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 17 ++++++++++++++++- .../dotty/tools/dotc/core/SymDenotations.scala | 13 ++++++++----- .../tools/dotc/core/tasty/TreeUnpickler.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Namer.scala | 2 +- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 2d0b175f0aad..2516d4bf406e 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -241,7 +241,7 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] => } /** The largest subset of {NoInits, PureInterface} that a - * trait enclosing this statement can have as flags. + * trait or class enclosing this statement can have as flags. */ def defKind(tree: Tree)(implicit ctx: Context): FlagSet = unsplice(tree) match { case EmptyTree | _: Import => NoInitsInterface @@ -254,6 +254,21 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] => case _ => EmptyFlags } + /** The largest subset of {NoInits, PureInterface} that a + * trait or class with these parents can have as flags. + */ + def parentsKind(parents: List[Tree])(implicit ctx: Context): FlagSet = parents match { + case Nil => NoInitsInterface + case Apply(_, _ :: _) :: _ => EmptyFlags + case _ :: parents1 => parentsKind(parents1) + } + + /** The largest subset of {NoInits, PureInterface} that a + * trait or class with this body can have as flags. + */ + def bodyKind(body: List[Tree])(implicit ctx: Context): FlagSet = + (NoInitsInterface /: body)((fs, stat) => fs & defKind(stat)) + /** Checks whether predicate `p` is true for all result parts of this expression, * where we zoom into Ifs, Matches, and Blocks. */ diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 024356574a71..83c865e27dbe 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -167,11 +167,14 @@ object SymDenotations { /** Unset given flags(s) of this denotation */ final def resetFlag(flags: FlagSet): Unit = { myFlags &~= flags } - /** Set applicable flags from `flags` which is a subset of {NoInits, PureInterface} */ - final def setNoInitsFlags(flags: FlagSet): Unit = { - val mask = if (myFlags.is(Trait)) NoInitsInterface else NoInits - setFlag(flags & mask) - } + /** Set applicable flags in {NoInits, PureInterface} + * @param parentFlags The flags that match the class or trait's parents + * @param bodyFlags The flags that match the class or trait's body + */ + final def setNoInitsFlags(parentFlags: FlagSet, bodyFlags: FlagSet): Unit = + setFlag( + if (myFlags.is(Trait)) NoInitsInterface & bodyFlags // no parents are initialized from a trait + else NoInits & bodyFlags & parentFlags) private def isCurrent(fs: FlagSet) = fs <= ( diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 621fc344fb83..0a5390f6c1c7 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -879,7 +879,7 @@ class TreeUnpickler(reader: TastyReader, else EmptyValDef cls.info = ClassInfo(cls.owner.thisType, cls, parentTypes, cls.unforcedDecls, if (self.isEmpty) NoType else self.tpt.tpe) - cls.setNoInitsFlags(fork.indexStats(end)) + cls.setNoInitsFlags(parentsKind(parents), fork.indexStats(end)) val constr = readIndexedDef().asInstanceOf[DefDef] val mappedParents = parents.map(_.changeOwner(localDummy, constr.symbol)) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 83a76159d192..fbf61391dc49 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -993,7 +993,7 @@ class Namer { typer: Typer => if (isDerivedValueClass(cls)) cls.setFlag(Final) cls.info = avoidPrivateLeaks(cls, cls.pos) cls.baseClasses.foreach(_.invalidateBaseTypeCache()) // we might have looked before and found nothing - cls.setNoInitsFlags((NoInitsInterface /: impl.body) ((fs, stat) => fs & untpd.defKind(stat))) + cls.setNoInitsFlags(parentsKind(parents), bodyKind(rest)) if (cls.isNoInitsClass) cls.primaryConstructor.setFlag(Stable) } } From 7f088906141f463ac3449b6f0d676750f49f495f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 23 Jun 2018 16:54:19 +0200 Subject: [PATCH 38/64] Fix ErrorMessagesTest Avoid "pure expression does nothing in statement position" warnings. --- .../dotty/tools/dotc/reporting/ErrorMessagesTests.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index d8327bc3a56b..0f7605966b98 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -163,7 +163,7 @@ class ErrorMessagesTests extends ErrorMessagesTest { """ |object Scope { | abstract class Concept - | new Concept() + | val x = new Concept() |} """.stripMargin } @@ -181,7 +181,7 @@ class ErrorMessagesTests extends ErrorMessagesTest { """ |object Scope { | trait Concept - | new Concept() + | val x = new Concept() |} """.stripMargin } @@ -508,7 +508,7 @@ class ErrorMessagesTests extends ErrorMessagesTest { """class Base |class RequiresBase { self: Base => } |object Scope { - | new RequiresBase + | val x = new RequiresBase |} |""".stripMargin } From be53b5b0894de14e1a89d526942a4812868ed762 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 23 Jun 2018 16:54:49 +0200 Subject: [PATCH 39/64] Don't drop impure arguments when reducing projections --- .../src/dotty/tools/dotc/typer/Inliner.scala | 14 ++- tests/run/reduce-projections.check | 48 +++++++++++ tests/run/reduce-projections.scala | 85 +++++++++++++++++++ 3 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 tests/run/reduce-projections.check create mode 100644 tests/run/reduce-projections.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 91063f7f5f0c..fca28d0b4dd4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -741,7 +741,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { /** 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, otherwise `tree` itself. + * 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 (meth.isTransparentMethod) { @@ -759,8 +760,15 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } val idx = cls.asClass.paramAccessors.indexWhere(matches(_, tree.symbol)) if (idx >= 0 && idx < args.length) { - inlining.println(i"projecting $tree -> ${args(idx)}") - return seq(prefix, args(idx)) + 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 evalOnce(arg)(seq(trailing, _)) + val reduced = seq(prefix, seq(leading, argInPlace)) + inlining.println(i"projecting $tree -> ${reduced}") + return reduced } case _ => } diff --git a/tests/run/reduce-projections.check b/tests/run/reduce-projections.check new file mode 100644 index 000000000000..edda274dcc92 --- /dev/null +++ b/tests/run/reduce-projections.check @@ -0,0 +1,48 @@ +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 diff --git a/tests/run/reduce-projections.scala b/tests/run/reduce-projections.scala new file mode 100644 index 000000000000..ba8d151b0fd5 --- /dev/null +++ b/tests/run/reduce-projections.scala @@ -0,0 +1,85 @@ + +class C(val x1: Int, val x2: Int, val x3: Int, val x4: Int) + +object Test { + 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) + } + + def main(args: Array[String]): Unit = { + f() + } +} \ No newline at end of file From bf290ca3b3dd1d14d199e2b624e2203dfbb05b7c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 23 Jun 2018 19:02:03 +0200 Subject: [PATCH 40/64] Fix: Don't drop impure arguments when reducing projections Previously we did treated an idempotent projected argument a if it was pure. This commit corrects that. --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 7 +++- .../src/dotty/tools/dotc/typer/Inliner.scala | 5 ++- tests/run/reduce-projections.scala | 37 +++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index cd842a16f77c..85e237383736 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1024,14 +1024,17 @@ 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) + 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))) } } + 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) } diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index fca28d0b4dd4..edf7556c4203 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -28,6 +28,7 @@ import transform.TypeUtils._ import reporting.trace import util.Positions.Position import util.Property +import ast.TreeInfo object Inliner { import tpd._ @@ -765,7 +766,9 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { val leading = collectImpure(0, idx) val trailing = collectImpure(idx + 1, args.length) val arg = args(idx) - val argInPlace = if (trailing.isEmpty) arg else evalOnce(arg)(seq(trailing, _)) + val argInPlace = + if (trailing.isEmpty) arg + else letBindUnless(TreeInfo.Pure, arg)(seq(trailing, _)) val reduced = seq(prefix, seq(leading, argInPlace)) inlining.println(i"projecting $tree -> ${reduced}") return reduced diff --git a/tests/run/reduce-projections.scala b/tests/run/reduce-projections.scala index ba8d151b0fd5..fc4099100404 100644 --- a/tests/run/reduce-projections.scala +++ b/tests/run/reduce-projections.scala @@ -2,6 +2,18 @@ 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 }, @@ -77,6 +89,31 @@ object Test { { 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 = { From bccac5e75fc9e4933b6bf9615cbd926f82a14a8d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 23 Jun 2018 21:04:29 +0200 Subject: [PATCH 41/64] Update check file --- tests/run/reduce-projections.check | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/run/reduce-projections.check b/tests/run/reduce-projections.check index edda274dcc92..b09296cec78b 100644 --- a/tests/run/reduce-projections.check +++ b/tests/run/reduce-projections.check @@ -46,3 +46,14 @@ 2 3 4 +=== +2 +3 +1 +3 +2 +2 +3 +2 +3 +4 From a60d1c29fcfa3a17e63d306380f5202aabd21eee Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 25 Jun 2018 18:54:57 +0200 Subject: [PATCH 42/64] Exclude memebrs of Predef from implicit inline environment Members of Predef are effectively global. It's therefore surprising that they should be part of the envrionment that comes with a transparent function. --- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 3 ++- tests/run/typelevel.scala | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index edf7556c4203..325e478545e7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -40,7 +40,8 @@ object Inliner { private val ContextualImplicit = new Property.StickyKey[Unit] def markContextualImplicit(tree: Tree)(implicit ctx: Context): Unit = - methPart(tree).putAttachment(ContextualImplicit, ()) + if (!defn.ScalaPredefModule.moduleClass.derivesFrom(tree.symbol.maybeOwner)) + methPart(tree).putAttachment(ContextualImplicit, ()) /** A key to be used in a context property that provides a map from enclosing implicit * value bindings to their right hand sides. diff --git a/tests/run/typelevel.scala b/tests/run/typelevel.scala index a137b554a91a..00b06895cc74 100644 --- a/tests/run/typelevel.scala +++ b/tests/run/typelevel.scala @@ -133,6 +133,20 @@ object Test extends App { 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 From 43f276cf9f33f5048b50a4ef8268504953ad9458 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 26 Jun 2018 17:13:11 +0200 Subject: [PATCH 43/64] Allow types applied to terms in syntax and parser --- .../src/dotty/tools/dotc/parsing/Parsers.scala | 17 ++++++++++++----- docs/docs/internals/syntax.md | 5 +++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 9ffa8cf9c52e..7d77248b3092 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -783,7 +783,8 @@ object Parsers { refinedTypeRest( withTypeRest( annotTypeRest( - simpleTypeRest(tuple))))) + appliedTypeRest( + simpleTypeRest(tuple)))))) } } } @@ -846,14 +847,20 @@ object Parsers { } else t - /** AnnotType ::= SimpleType {Annotation} + /** AnnotType ::= AppliedType {Annotation} */ - def annotType(): Tree = annotTypeRest(simpleType()) + def annotType(): Tree = annotTypeRest(appliedType()) def annotTypeRest(t: Tree): Tree = if (in.token == AT) annotTypeRest(atPos(startOffset(t)) { Annotated(t, annot()) }) else t + /** AppiedType ::= SimpleType {ParArgumentExprs} + */ + def appliedType(): Tree = appliedTypeRest(simpleType()) + + def appliedTypeRest(t: Tree) = parArgumentExprss(t) + /** SimpleType ::= SimpleType TypeArgs * | SimpleType `#' id * | StableId @@ -2312,11 +2319,11 @@ object Parsers { /* -------- TEMPLATES ------------------------------------------- */ - /** ConstrApp ::= SimpleType {ParArgumentExprs} + /** ConstrApp ::= SimpleType {Annotation} {ParArgumentExprs} */ val constrApp = () => { // Using Ident(nme.ERROR) to avoid causing cascade errors on non-user-written code - val t = checkWildcard(annotType(), fallbackTree = Ident(nme.ERROR)) + val t = checkWildcard(annotTypeRest(simpleType()), fallbackTree = Ident(nme.ERROR)) if (in.token == LPAREN) parArgumentExprss(wrapNew(t)) else t } diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index f275cbcd497d..0419d27f311d 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -128,7 +128,8 @@ TypedFunParam ::= id ‘:’ Type InfixType ::= RefinedType {id [nl] RefinedType} InfixOp(t1, op, t2) RefinedType ::= WithType {[nl] Refinement} RefinedTypeTree(t, ds) WithType ::= AnnotType {‘with’ AnnotType} (deprecated) -AnnotType ::= SimpleType {Annotation} Annotated(t, annot) +AnnotType ::= AppliedType {Annotation} Annotated(t, annot) +AppliedType ::= SimpleType {ParArgumentExprs} Apply(t, args) SimpleType ::= SimpleType TypeArgs AppliedTypeTree(t, args) | SimpleType ‘#’ id Select(t, name) | StableId @@ -337,7 +338,7 @@ EnumDef ::= id ClassConstr [‘extends’ [ConstrApps]] EnumBody TemplateOpt ::= [‘extends’ Template | [nl] TemplateBody] Template ::= ConstrApps [TemplateBody] | TemplateBody Template(constr, parents, self, stats) ConstrApps ::= ConstrApp {‘with’ ConstrApp} -ConstrApp ::= AnnotType {ArgumentExprs} Apply(tp, args) +ConstrApp ::= SimpleType {Annotation} {ParArgumentExprs} Apply(tp, args) ConstrExpr ::= SelfInvocation | ConstrBlock SelfInvocation ::= ‘this’ ArgumentExprs {ArgumentExprs} From f64241c89529d27a4963918bf07ea64319b5f4c8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 26 Jun 2018 22:59:20 +0200 Subject: [PATCH 44/64] Syntax for computed type definitions --- .../src/dotty/tools/dotc/ast/Desugar.scala | 2 +- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 6 +++ compiler/src/dotty/tools/dotc/ast/Trees.scala | 15 ++++-- compiler/src/dotty/tools/dotc/ast/untpd.scala | 2 +- .../src/dotty/tools/dotc/core/Comments.scala | 2 +- .../tools/dotc/parsing/JavaParsers.scala | 2 +- .../dotty/tools/dotc/parsing/Parsers.scala | 53 ++++++++++++++----- .../dotc/reporting/diagnostic/messages.scala | 2 +- docs/docs/internals/syntax.md | 6 ++- 9 files changed, 65 insertions(+), 25 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 5f42c748bd33..f01bd304a644 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -201,7 +201,7 @@ object desugar { case (vparam :: vparams) :: vparamss1 => def defaultGetter: DefDef = DefDef( - name = DefaultGetterName(meth.name, n), + name = DefaultGetterName(meth.name.asTermName, n), tparams = meth.tparams.map(tparam => dropContextBound(toDefParam(tparam))), vparamss = takeUpTo(normalizedVparamss.nestedMap(toDefParam), n), tpt = TypeTree(), diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 2516d4bf406e..8b454674b547 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -339,6 +339,12 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped] case _ => false } + /** Is this the RHS of a transparnt type def, which needs to be represented as a DefDef? */ + def isTypeDefRHS(tree: Tree): Boolean = tree match { + case tree: If => true + case _ => false + } + // todo: fill with other methods from TreeInfo that only apply to untpd.Tree's } diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index a100c14cd57c..c5c9028259e9 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -492,8 +492,10 @@ object Trees { /** if cond then thenp else elsep */ case class If[-T >: Untyped] private[ast] (cond: Tree[T], thenp: Tree[T], elsep: Tree[T]) - extends TermTree[T] { + extends Tree[T] { type ThisTree[-T >: Untyped] = If[T] + override def isTerm = thenp.isTerm + override def isType = thenp.isType } /** A closure with an environment and a reference to a method. @@ -697,8 +699,11 @@ object Trees { protected def force(x: AnyRef) = preRhs = x } - /** mods def name[tparams](vparams_1)...(vparams_n): tpt = rhs */ - case class DefDef[-T >: Untyped] private[ast] (name: TermName, tparams: List[TypeDef[T]], + /** mods def name[tparams](vparams_1)...(vparams_n): tpt = rhs + * or + * mods type[tparams](vparams_1)...(vparams_n): tpt = rhs + */ + case class DefDef[-T >: Untyped] private[ast] (name: Name, tparams: List[TypeDef[T]], vparamss: List[List[ValDef[T]]], tpt: Tree[T], private var preRhs: LazyTree) extends ValOrDefDef[T] { type ThisTree[-T >: Untyped] = DefDef[T] @@ -1085,7 +1090,7 @@ object Trees { case tree: ValDef if (name == tree.name) && (tpt eq tree.tpt) && (rhs eq tree.unforcedRhs) => tree case _ => finalize(tree, untpd.ValDef(name, tpt, rhs)) } - def DefDef(tree: Tree)(name: TermName, tparams: List[TypeDef], vparamss: List[List[ValDef]], tpt: Tree, rhs: LazyTree): DefDef = tree match { + def DefDef(tree: Tree)(name: Name, tparams: List[TypeDef], vparamss: List[List[ValDef]], tpt: Tree, rhs: LazyTree): DefDef = tree match { case tree: DefDef if (name == tree.name) && (tparams eq tree.tparams) && (vparamss eq tree.vparamss) && (tpt eq tree.tpt) && (rhs eq tree.unforcedRhs) => tree case _ => finalize(tree, untpd.DefDef(name, tparams, vparamss, tpt, rhs)) } @@ -1132,7 +1137,7 @@ object Trees { UnApply(tree: Tree)(fun, implicits, patterns) def ValDef(tree: ValDef)(name: TermName = tree.name, tpt: Tree = tree.tpt, rhs: LazyTree = tree.unforcedRhs): ValDef = ValDef(tree: Tree)(name, tpt, rhs) - def DefDef(tree: DefDef)(name: TermName = tree.name, tparams: List[TypeDef] = tree.tparams, vparamss: List[List[ValDef]] = tree.vparamss, tpt: Tree = tree.tpt, rhs: LazyTree = tree.unforcedRhs): DefDef = + def DefDef(tree: DefDef)(name: Name = tree.name, tparams: List[TypeDef] = tree.tparams, vparamss: List[List[ValDef]] = tree.vparamss, tpt: Tree = tree.tpt, rhs: LazyTree = tree.unforcedRhs): DefDef = DefDef(tree: Tree)(name, tparams, vparamss, tpt, rhs) def TypeDef(tree: TypeDef)(name: TypeName = tree.name, rhs: Tree = tree.rhs): TypeDef = TypeDef(tree: Tree)(name, rhs) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 74734a158109..de5a371b75c6 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -294,7 +294,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def Alternative(trees: List[Tree]): Alternative = new Alternative(trees) def UnApply(fun: Tree, implicits: List[Tree], patterns: List[Tree]): UnApply = new UnApply(fun, implicits, patterns) def ValDef(name: TermName, tpt: Tree, rhs: LazyTree): ValDef = new ValDef(name, tpt, rhs) - def DefDef(name: TermName, tparams: List[TypeDef], vparamss: List[List[ValDef]], tpt: Tree, rhs: LazyTree): DefDef = new DefDef(name, tparams, vparamss, tpt, rhs) + def DefDef(name: Name, tparams: List[TypeDef], vparamss: List[List[ValDef]], tpt: Tree, rhs: LazyTree): DefDef = new DefDef(name, tparams, vparamss, tpt, rhs) def TypeDef(name: TypeName, rhs: Tree): TypeDef = new TypeDef(name, rhs) def Template(constr: DefDef, parents: List[Tree], self: ValDef, body: LazyTreeList): Template = new Template(constr, parents, self, body) def Import(expr: Tree, selectors: List[untpd.Tree]): Import = new Import(expr, selectors) diff --git a/compiler/src/dotty/tools/dotc/core/Comments.scala b/compiler/src/dotty/tools/dotc/core/Comments.scala index 0d38791efacc..db0aee43c12d 100644 --- a/compiler/src/dotty/tools/dotc/core/Comments.scala +++ b/compiler/src/dotty/tools/dotc/core/Comments.scala @@ -121,7 +121,7 @@ object Comments { tree match { case tree: untpd.DefDef => - val newName = ctx.freshNames.newName(tree.name, NameKinds.DocArtifactName) + val newName = ctx.freshNames.newName(tree.name.toTermName, NameKinds.DocArtifactName) untpd.DefDef(newName, tree.tparams, tree.vparamss, tree.tpt, tree.rhs) case _ => ctx.error(ProperDefinitionNotFound(), codePos) diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index 5c203d123f4a..024026c984eb 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -784,7 +784,7 @@ object JavaParsers { // If the annotation has a default value we don't need to parse it, providing // any value at all is enough to typecheck usages of annotations correctly. val defaultParam = if (hasDefault) unimplementedExpr else EmptyTree - makeParam(dd.name, dd.tpt, defaultParam) + makeParam(dd.name.asTermName, dd.tpt, defaultParam) } val constr = DefDef(nme.CONSTRUCTOR, List(), List(constructorParams), TypeTree(), EmptyTree).withMods(Modifiers(Flags.JavaDefined)) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 7d77248b3092..af259d1dae5e 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -46,7 +46,7 @@ object Parsers { } @sharable object ParamOwner extends Enumeration { - val Class, Type, TypeParam, Def = Value + val Class, Type, TypeParam, Def, Constructor = Value } private implicit class AddDeco(val buf: ListBuffer[Tree]) extends AnyVal { @@ -714,6 +714,7 @@ object Parsers { } /* ------------- TYPES ------------------------------------------------------ */ + /** Same as [[typ]], but if this results in a wildcard it emits a syntax error and * returns a tree for type `Any` instead. */ @@ -1055,6 +1056,21 @@ object Parsers { case None => t } + /** TypeRHS ::= ‘if’ Expr ‘then’ TypeRHS ‘else’ TypeRHS + * | Type + */ + def typeRHS(): Tree = + if (in.token == IF) + atPos(in.skipToken()) { + val cond = typeRHS() + accept(THEN) + val thenp = typeRHS() + accept(ELSE) + val elsep = expr() + If(cond, thenp, elsep) + } + else toplevelTyp() + /* ----------- EXPRESSIONS ------------------------------------------------ */ /** EqualsExpr ::= `=' Expr @@ -1831,14 +1847,15 @@ object Parsers { */ def typeParamClause(ownerKind: ParamOwner.Value): List[TypeDef] = inBrackets { def typeParam(): TypeDef = { - val isConcreteOwner = ownerKind == ParamOwner.Class || ownerKind == ParamOwner.Def + val isDefOwner = ownerKind == ParamOwner.Def || ownerKind == ParamOwner.Constructor + val isConcreteOwner = isDefOwner || ownerKind == ParamOwner.Class val start = in.offset val mods = atPos(start) { annotsAsMods() | { if (ownerKind == ParamOwner.Class) Param | PrivateLocal else Param } | { - if (ownerKind != ParamOwner.Def) + if (!isDefOwner) if (isIdent(nme.raw.PLUS)) { in.nextToken(); Covariant } else if (isIdent(nme.raw.MINUS)) { in.nextToken(); Contravariant } else EmptyFlags @@ -1875,14 +1892,14 @@ object Parsers { * DefParam ::= {Annotation} [`inline'] Param * Param ::= id `:' ParamType [`=' Expr] */ - def paramClauses(owner: Name, ofCaseClass: Boolean = false): List[List[ValDef]] = { + def paramClauses(ownerKind: ParamOwner.Value, ofCaseClass: Boolean = false): List[List[ValDef]] = { var imods: Modifiers = EmptyModifiers var implicitOffset = -1 // use once var firstClauseOfCaseClass = ofCaseClass def param(): ValDef = { val start = in.offset var mods = annotsAsMods() - if (owner.isTypeName) { + if (ownerKind == ParamOwner.Class) { mods = addFlag(modifiers(start = mods), ParamAccessor) mods = atPos(start, in.offset) { @@ -1909,7 +1926,7 @@ object Parsers { atPos(start, nameStart) { val name = ident() accept(COLON) - if (in.token == ARROW && owner.isTypeName && !(mods is Local)) + if (in.token == ARROW && ownerKind == ParamOwner.Class && !(mods is Local)) syntaxError(VarValParametersMayNotBeCallByName(name, mods is Mutable)) val tpt = paramType() val default = @@ -1936,7 +1953,7 @@ object Parsers { funArgMods() } } - funArgMods() + if (ownerKind != ParamOwner.Type) funArgMods() commaSeparated(() => param()) } @@ -1953,7 +1970,7 @@ object Parsers { } val start = in.offset val result = clauses() - if (owner == nme.CONSTRUCTOR && (result.isEmpty || (result.head take 1 exists (_.mods is Implicit)))) { + if (ownerKind == ParamOwner.Constructor && (result.isEmpty || (result.head take 1 exists (_.mods is Implicit)))) { in.token match { case LBRACKET => syntaxError("no type parameters allowed here") case EOF => incompleteInputError(AuxConstructorNeedsNonImplicitParameter()) @@ -2121,7 +2138,7 @@ object Parsers { } if (in.token == THIS) { in.nextToken() - val vparamss = paramClauses(nme.CONSTRUCTOR) + val vparamss = paramClauses(ParamOwner.Constructor) if (in.isScala2Mode) newLineOptWhenFollowedBy(LBRACE) val rhs = { if (!(in.token == LBRACE && scala2ProcedureSyntax(""))) accept(EQUALS) @@ -2132,7 +2149,7 @@ object Parsers { val mods1 = addFlag(mods, Method) val name = ident() val tparams = typeParamClauseOpt(ParamOwner.Def) - val vparamss = paramClauses(name) + val vparamss = paramClauses(ParamOwner.Def) var tpt = fromWithinReturnType(typedOpt()) if (in.isScala2Mode) newLineOptWhenFollowedBy(LBRACE) val rhs = @@ -2183,19 +2200,27 @@ object Parsers { Block(stats, Literal(Constant(()))) } - /** TypeDef ::= type id [TypeParamClause] `=' Type - * TypeDcl ::= type id [TypeParamClause] TypeBounds + /** TypeDcl ::= id [TypTypeParamClause] {DefParamClause} [‘:’ Type] ‘=’ TypeRHS + * | id [HkTypeParamClause] TypeBounds */ def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = { newLinesOpt() atPos(start, nameStart) { val name = ident().toTypeName val tparams = typeParamClauseOpt(ParamOwner.Type) + val vparamss = paramClauses(ParamOwner.Type) + val tpt = typedOpt() + val isDef = !vparamss.isEmpty || !tpt.isEmpty in.token match { case EQUALS => in.nextToken() - TypeDef(name, lambdaAbstract(tparams, toplevelTyp())).withMods(mods).setComment(in.getDocComment(start)) + val rhs = typeRHS() + val res = + if (isTypeDefRHS(rhs) || isDef) DefDef(name, tparams, vparamss, tpt, rhs) + else TypeDef(name, lambdaAbstract(tparams, rhs)) + res.withMods(mods).setComment(in.getDocComment(start)) case SUPERTYPE | SUBTYPE | SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | EOF => + if (isDef) syntaxError("`=' expected") TypeDef(name, lambdaAbstract(tparams, typeBounds())).withMods(mods).setComment(in.getDocComment(start)) case _ => syntaxErrorOrIncomplete(ExpectedTypeBoundOrEquals(in.token)) @@ -2245,7 +2270,7 @@ object Parsers { def classConstr(owner: Name, isCaseClass: Boolean = false): DefDef = atPos(in.lastOffset) { val tparams = typeParamClauseOpt(ParamOwner.Class) val cmods = fromWithinClassConstr(constrModsOpt(owner)) - val vparamss = paramClauses(owner, isCaseClass) + val vparamss = paramClauses(ParamOwner.Class, isCaseClass) makeConstructor(tparams, vparamss).withMods(cmods) } diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 79f5c33d8779..3457009baf30 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -1260,7 +1260,7 @@ object messages { |""".stripMargin } - case class OverloadedOrRecursiveMethodNeedsResultType(tree: Names.TermName)(implicit ctx: Context) + case class OverloadedOrRecursiveMethodNeedsResultType(tree: Names.Name)(implicit ctx: Context) extends Message(OverloadedOrRecursiveMethodNeedsResultTypeID) { val kind = "Syntax" val msg = hl"""overloaded or recursive method ${tree} needs return type""" diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 0419d27f311d..ea49adef5f9f 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -151,6 +151,9 @@ NamedTypeArgs ::= ‘[’ NamedTypeArg {‘,’ NamedTypeArg} ‘]’ Refinement ::= ‘{’ [RefineDcl] {semi [RefineDcl]} ‘}’ ds TypeBounds ::= [‘>:’ Type] [‘<:’ Type] | INT TypeBoundsTree(lo, hi) TypeParamBounds ::= TypeBounds {‘<%’ Type} {‘:’ Type} ContextBounds(typeBounds, tps) + +TypeRHS ::= ‘if’ Expr ‘then’ TypeRHS ‘else’ TypeRHS If(cond, thenp, elsep) + | Type ``` ### Expressions @@ -310,7 +313,8 @@ ValDcl ::= ids ‘:’ Type VarDcl ::= ids ‘:’ Type PatDef(_, ids, tpe, EmptyTree) DefDcl ::= DefSig [‘:’ Type] DefDef(_, name, tparams, vparamss, tpe, EmptyTree) DefSig ::= id [DefTypeParamClause] DefParamClauses -TypeDcl ::= id [TypTypeParamClause] [‘=’ Type] TypeDefTree(_, name, tparams, tpt) +TypeDcl ::= id [TypTypeParamClause] {DefParamClause} [‘:’ Type] TypeDefTree(_, name, tparams, tpt) + ‘=’ TypeRHS | id [HkTypeParamClause] TypeBounds TypeDefTree(_, name, tparams, bounds) Def ::= ‘val’ PatDef From 06dbeaeedce8278f03a2bf789037ad2d02426b2d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 27 Jun 2018 16:58:53 +0200 Subject: [PATCH 45/64] Drop unused HigherKinded flag Re-use the slot for `TypeMethod` (currently neither used nor set) --- compiler/src/dotty/tools/dotc/core/Flags.scala | 4 ++-- compiler/src/dotty/tools/dotc/typer/Namer.scala | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 2fc48608dfe6..83ceb5f72d13 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -223,7 +223,7 @@ object Flags { /** A method symbol. */ final val Method = termFlag(7, "") - final val HigherKinded = typeFlag(7, "") + final val TypeMethod = typeFlag(7, "") /** A (term or type) parameter to a class or method */ final val Param = commonFlag(8, "") @@ -456,7 +456,7 @@ object Flags { /** Flags that are not (re)set when completing the denotation */ final val FromStartFlags = Module | Package | Deferred | Method.toCommonFlags | - HigherKinded.toCommonFlags | Param | ParamAccessor.toCommonFlags | + Param | ParamAccessor.toCommonFlags | Scala2ExistentialCommon | Mutable.toCommonFlags | Touched | JavaStatic | CovariantOrOuter | ContravariantOrLabel | CaseAccessor.toCommonFlags | NonMember | ImplicitCommon | Permanent | Synthetic | diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index fbf61391dc49..6f7805f12b8a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -342,10 +342,6 @@ class Namer { typer: Typer => val isDeferred = lacksDefinition(tree) val deferred = if (isDeferred) Deferred else EmptyFlags val method = if (tree.isInstanceOf[DefDef]) Method else EmptyFlags - val higherKinded = tree match { - case TypeDef(_, LambdaTypeTree(_, _)) if isDeferred => HigherKinded - case _ => EmptyFlags - } // to complete a constructor, move one context further out -- this // is the context enclosing the class. Note that the context in which a @@ -363,7 +359,7 @@ class Namer { typer: Typer => case _ => new Completer(tree)(cctx) } val info = adjustIfModule(completer, tree) - createOrRefine[Symbol](tree, name, flags | deferred | method | higherKinded, + createOrRefine[Symbol](tree, name, flags | deferred | method, _ => info, (fs, _, pwithin) => ctx.newSymbol(ctx.owner, name, fs, info, pwithin, tree.namePos)) case tree: Import => From 81950efe2fa58c0434cefc336e562cdf84cd0efb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 27 Jun 2018 17:02:26 +0200 Subject: [PATCH 46/64] Coomputed type defs have a TypeBounds as declared result type. --- .../dotty/tools/dotc/parsing/Parsers.scala | 35 +++++++++---------- docs/docs/internals/syntax.md | 4 +-- tests/run/typelevel1.scala | 3 +- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index af259d1dae5e..36ea9087eba8 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1062,11 +1062,11 @@ object Parsers { def typeRHS(): Tree = if (in.token == IF) atPos(in.skipToken()) { - val cond = typeRHS() + val cond = expr() accept(THEN) val thenp = typeRHS() accept(ELSE) - val elsep = expr() + val elsep = typeRHS() If(cond, thenp, elsep) } else toplevelTyp() @@ -2200,7 +2200,7 @@ object Parsers { Block(stats, Literal(Constant(()))) } - /** TypeDcl ::= id [TypTypeParamClause] {DefParamClause} [‘:’ Type] ‘=’ TypeRHS + /** TypeDcl ::= id [TypTypeParamClause] {DefParamClause} TypeBounds ‘=’ TypeRHS * | id [HkTypeParamClause] TypeBounds */ def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = { @@ -2209,23 +2209,22 @@ object Parsers { val name = ident().toTypeName val tparams = typeParamClauseOpt(ParamOwner.Type) val vparamss = paramClauses(ParamOwner.Type) - val tpt = typedOpt() - val isDef = !vparamss.isEmpty || !tpt.isEmpty - in.token match { - case EQUALS => + val isBounded = in.token == SUPERTYPE || in.token == SUBTYPE + val bounds = typeBounds() + val res = + if (in.token == EQUALS) { in.nextToken() val rhs = typeRHS() - val res = - if (isTypeDefRHS(rhs) || isDef) DefDef(name, tparams, vparamss, tpt, rhs) - else TypeDef(name, lambdaAbstract(tparams, rhs)) - res.withMods(mods).setComment(in.getDocComment(start)) - case SUPERTYPE | SUBTYPE | SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | EOF => - if (isDef) syntaxError("`=' expected") - TypeDef(name, lambdaAbstract(tparams, typeBounds())).withMods(mods).setComment(in.getDocComment(start)) - case _ => - syntaxErrorOrIncomplete(ExpectedTypeBoundOrEquals(in.token)) - EmptyTree - } + if (isTypeDefRHS(rhs) || isBounded || vparamss.nonEmpty) + DefDef(name, tparams, vparamss, bounds, rhs) + else + TypeDef(name, lambdaAbstract(tparams, rhs)) + } + else { + if (vparamss.nonEmpty) syntaxError("`=' expected") + TypeDef(name, lambdaAbstract(tparams, bounds)) + } + res.withMods(mods).setComment(in.getDocComment(start)) } } diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index ea49adef5f9f..41bd23403617 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -313,9 +313,9 @@ ValDcl ::= ids ‘:’ Type VarDcl ::= ids ‘:’ Type PatDef(_, ids, tpe, EmptyTree) DefDcl ::= DefSig [‘:’ Type] DefDef(_, name, tparams, vparamss, tpe, EmptyTree) DefSig ::= id [DefTypeParamClause] DefParamClauses -TypeDcl ::= id [TypTypeParamClause] {DefParamClause} [‘:’ Type] TypeDefTree(_, name, tparams, tpt) +TypeDcl ::= id [TypTypeParamClause] {DefParamClause} TypeBounds DefDef(name, tparams, vparamss, bounds, rhs) ‘=’ TypeRHS - | id [HkTypeParamClause] TypeBounds TypeDefTree(_, name, tparams, bounds) + | id [HkTypeParamClause] TypeBounds TypeDef(name, tparams, bounds) Def ::= ‘val’ PatDef | ‘var’ VarDef diff --git a/tests/run/typelevel1.scala b/tests/run/typelevel1.scala index 3a490b596384..18241a1990db 100644 --- a/tests/run/typelevel1.scala +++ b/tests/run/typelevel1.scala @@ -48,5 +48,6 @@ object Test extends App { val zs1 = zs(1) val zs2 = zs(2) val zs3 = zs(3) - def zs4 = zs(4) + val zs4 = zs(4) + def zs5 = zs(5) } \ No newline at end of file From c9f753526625cc89262182bae89df56a403ace1b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 28 Jun 2018 16:32:23 +0200 Subject: [PATCH 47/64] Add HKTermLambdas This completes the square of lambda types --- .../src/dotty/tools/dotc/core/Types.scala | 47 ++++++++++++++----- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index b90a5bde2c98..daa135b3fe2b 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2955,6 +2955,16 @@ object Types { extends LambdaTypeCompanion[TermName, Type, LT] { def toPInfo(tp: Type)(implicit ctx: Context): Type = tp def syntheticParamName(n: Int) = nme.syntheticParamName(n) + + def checkValid(mt: TermLambda)(implicit ctx: Context): mt.type = { + if (Config.checkTermLambdas) + for ((paramInfo, idx) <- mt.paramInfos.zipWithIndex) + paramInfo.foreachPart { + case TermParamRef(`mt`, j) => assert(j < idx, mt) + case _ => + } + mt + } } abstract class TypeLambdaCompanion[LT <: TypeLambda] @@ -2967,6 +2977,8 @@ object Types { } abstract class MethodTypeCompanion extends TermLambdaCompanion[MethodType] { self => + final def apply(paramNames: List[TermName])(paramInfosExp: MethodType => List[Type], resultTypeExp: MethodType => Type)(implicit ctx: Context): MethodType = + checkValid(unique(new CachedMethodType(paramNames)(paramInfosExp, resultTypeExp, self))) /** Produce method type from parameter symbols, with special mappings for repeated * and inline parameters: @@ -2987,19 +2999,6 @@ object Types { tl => params.map(p => tl.integrate(params, paramInfo(p))), tl => tl.integrate(params, resultType)) } - - final def apply(paramNames: List[TermName])(paramInfosExp: MethodType => List[Type], resultTypeExp: MethodType => Type)(implicit ctx: Context): MethodType = - checkValid(unique(new CachedMethodType(paramNames)(paramInfosExp, resultTypeExp, self))) - - def checkValid(mt: MethodType)(implicit ctx: Context): mt.type = { - if (Config.checkMethodTypes) - for ((paramInfo, idx) <- mt.paramInfos.zipWithIndex) - paramInfo.foreachPart { - case TermParamRef(`mt`, j) => assert(j < idx, mt) - case _ => - } - mt - } } object MethodType extends MethodTypeCompanion { @@ -3079,6 +3078,20 @@ object Types { protected def prefixString = "HKTypeLambda" } + class HKTermLambda(val paramNames: List[TermName])( + paramInfosExp: HKTermLambda => List[Type], resultTypeExp: HKTermLambda => Type) + extends HKLambda with TermLambda { + type This = HKTermLambda + def companion = HKTermLambda + + val paramInfos: List[Type] = paramInfosExp(this) + val resType: Type = resultTypeExp(this) + + assert(resType.isInstanceOf[TermType], this) + + protected def prefixString = "HKTermLambda" + } + /** The type of a polymorphic method. It has the same form as HKTypeLambda, * except it applies to terms and parameters do not have variances. */ @@ -3155,6 +3168,14 @@ object Types { } } + object HKTermLambda extends TermLambdaCompanion[HKTermLambda] { + def apply(paramNames: List[TermName])( + paramInfosExp: HKTermLambda => List[Type], + resultTypeExp: HKTermLambda => Type)(implicit ctx: Context): HKTermLambda = { + checkValid(unique(new HKTermLambda(paramNames)(paramInfosExp, resultTypeExp))) + } + } + object PolyType extends TypeLambdaCompanion[PolyType] { def apply(paramNames: List[TypeName])( paramInfosExp: PolyType => List[TypeBounds], From 34ac63c2e6ab9f85838cad7a4a23b68bd93f1f5d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 28 Jun 2018 17:26:43 +0200 Subject: [PATCH 48/64] Make `Transparent` a common flag --- compiler/src/dotty/tools/dotc/core/Flags.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 83ceb5f72d13..752e28da522a 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -364,7 +364,7 @@ object Flags { final val DefaultMethod = termFlag(38, "") /** Labelled with `transparent` modifier */ - final val Transparent = termFlag(39, "transparent") + final val Transparent = commonFlag(39, "transparent") /** Symbol is an enum class or enum case (if used with case) */ final val Enum = commonFlag(40, "") From d5619ae4d57483e3446bb432f9b614789cfe42fe Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 28 Jun 2018 17:28:38 +0200 Subject: [PATCH 49/64] Make Term Lambda parameters distribute into bounds Analogous to type lambda parameters --- .../src/dotty/tools/dotc/config/Config.scala | 4 +- .../src/dotty/tools/dotc/core/Types.scala | 39 ++++++++++--------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index ea96d9dcd5d9..7b5f5656260a 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -97,9 +97,9 @@ object Config { */ final val checkHKApplications = false - /** If this flag is set, method types are checked for valid parameter references + /** If this flag is set, term lambda types are checked for valid parameter references */ - final val checkMethodTypes = false + final val checkTermLambdas = false /** If this flag is set, it is checked that TypeRefs don't refer directly * to themselves. diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index daa135b3fe2b..235c5602c846 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3131,22 +3131,7 @@ object Types { protected def prefixString = "PolyType" } - object HKTypeLambda extends TypeLambdaCompanion[HKTypeLambda] { - def apply(paramNames: List[TypeName])( - paramInfosExp: HKTypeLambda => List[TypeBounds], - resultTypeExp: HKTypeLambda => Type)(implicit ctx: Context): HKTypeLambda = { - unique(new HKTypeLambda(paramNames)(paramInfosExp, resultTypeExp)) - } - - def unapply(tl: HKTypeLambda): Some[(List[LambdaParam], Type)] = - Some((tl.typeParams, tl.resType)) - - def any(n: Int)(implicit ctx: Context) = - apply(syntheticParamNames(n))( - pt => List.fill(n)(TypeBounds.empty), pt => defn.AnyType) - - override def paramName(param: ParamInfo.Of[TypeName])(implicit ctx: Context): TypeName = - param.paramName.withVariance(param.paramVariance) + trait HKCompanion[N <: Name, PInfo <: Type, LT <: LambdaType] extends LambdaTypeCompanion[N, PInfo, LT] { /** Distributes Lambda inside type bounds. Examples: * @@ -3154,7 +3139,7 @@ object Types { * type T[X] <: U becomes type T >: Nothign <: ([X] -> U) * type T[X] >: L <: U becomes type T >: ([X] -> L) <: ([X] -> U) */ - override def fromParams[PI <: ParamInfo.Of[TypeName]](params: List[PI], resultType: Type)(implicit ctx: Context): Type = { + override def fromParams[PI <: ParamInfo.Of[N]](params: List[PI], resultType: Type)(implicit ctx: Context): Type = { def expand(tp: Type) = super.fromParams(params, tp) resultType match { case rt: TypeAlias => @@ -3168,7 +3153,25 @@ object Types { } } - object HKTermLambda extends TermLambdaCompanion[HKTermLambda] { + object HKTypeLambda extends TypeLambdaCompanion[HKTypeLambda] with HKCompanion[TypeName, TypeBounds, HKTypeLambda] { + def apply(paramNames: List[TypeName])( + paramInfosExp: HKTypeLambda => List[TypeBounds], + resultTypeExp: HKTypeLambda => Type)(implicit ctx: Context): HKTypeLambda = { + unique(new HKTypeLambda(paramNames)(paramInfosExp, resultTypeExp)) + } + + def unapply(tl: HKTypeLambda): Some[(List[LambdaParam], Type)] = + Some((tl.typeParams, tl.resType)) + + def any(n: Int)(implicit ctx: Context) = + apply(syntheticParamNames(n))( + pt => List.fill(n)(TypeBounds.empty), pt => defn.AnyType) + + override def paramName(param: ParamInfo.Of[TypeName])(implicit ctx: Context): TypeName = + param.paramName.withVariance(param.paramVariance) + } + + object HKTermLambda extends TermLambdaCompanion[HKTermLambda] with HKCompanion[TermName, Type, HKTermLambda] { def apply(paramNames: List[TermName])( paramInfosExp: HKTermLambda => List[Type], resultTypeExp: HKTermLambda => Type)(implicit ctx: Context): HKTermLambda = { From de2b06acfbcd76ead7d7f68c1786234dfd2d1713 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 28 Jun 2018 18:01:09 +0200 Subject: [PATCH 50/64] First stab at type methods Allow types to be parameterized over terms. Parameterized type definitions are treated analogously to transparent methods. --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 2 +- .../tools/dotc/core/SymDenotations.scala | 3 +- .../src/dotty/tools/dotc/core/Types.scala | 6 ++ .../tools/dotc/core/tasty/TreeUnpickler.scala | 6 +- .../tools/dotc/printing/PlainPrinter.scala | 2 +- .../tools/dotc/printing/RefinedPrinter.scala | 15 +++-- .../tools/dotc/reporting/StoreReporter.scala | 3 +- .../dotty/tools/dotc/transform/Erasure.scala | 56 +++++++++--------- .../tools/dotc/transform/ResolveSuper.scala | 4 +- .../tools/dotc/transform/TreeChecker.scala | 11 ++-- .../dotty/tools/dotc/typer/Applications.scala | 58 +++++++++---------- .../src/dotty/tools/dotc/typer/Inliner.scala | 14 ++--- .../src/dotty/tools/dotc/typer/Namer.scala | 58 ++++++++++++------- .../dotty/tools/dotc/typer/ProtoTypes.scala | 14 +++-- .../dotty/tools/dotc/typer/TypeAssigner.scala | 4 +- .../src/dotty/tools/dotc/typer/Typer.scala | 32 +++++++--- tests/run/typelevel2.scala | 37 ++++++++++++ 17 files changed, 208 insertions(+), 117 deletions(-) create mode 100644 tests/run/typelevel2.scala diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 85e237383736..efdd3a00edd3 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -106,7 +106,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { Closure(Nil, call, targetTpt)) } - /** A closure whole anonymous function has the given method type */ + /** A closure whose anonymous function has the given method type */ def Lambda(tpe: MethodType, rhsFn: List[Tree] => Tree)(implicit ctx: Context): Block = { val meth = ctx.newSymbol(ctx.owner, nme.ANON_FUN, Synthetic | Method, tpe) Closure(meth, tss => rhsFn(tss.head).changeOwner(ctx.owner, meth)) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 83c865e27dbe..813b67e1d061 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -792,7 +792,8 @@ object SymDenotations { is(InlineMethod, butNot = Accessor) def isTransparentMethod(implicit ctx: Context): Boolean = - is(TransparentMethod, butNot = AccessorOrSynthetic) + is(TransparentMethod, butNot = AccessorOrSynthetic) || + is(TypeMethod) def isInlineableMethod(implicit ctx: Context) = isInlinedMethod || isTransparentMethod diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 235c5602c846..0d77795fc19c 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -993,6 +993,12 @@ object Types { case _ => this } + /** If this type is a typeref with a type lambda as alias or upper bound, widen to the lambda */ + final def toLambda(implicit ctx: Context): Type = widen match { + case tp: TypeRef if tp.info.hiBound.isInstanceOf[LambdaType] => tp.info.hiBound + case tp => tp + } + /** If this type contains embedded union types, replace them by their joins. * "Embedded" means: inside intersectons or recursive types, or in prefixes of refined types. * If an embedded union is found, we first try to simplify or eliminate it by diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 0a5390f6c1c7..714a4f5be246 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -770,11 +770,11 @@ class TreeUnpickler(reader: TastyReader, val tparams = readParams[TypeDef](TYPEPARAM)(localCtx) val vparamss = readParamss(localCtx) val tpt = readTpt()(localCtx) - val typeParams = tparams.map(_.symbol) + val typeParams = tparams.map(_.symbol.asType) val valueParamss = ctx.normalizeIfConstructor( - vparamss.nestedMap(_.symbol), name == nme.CONSTRUCTOR) + vparamss.nestedMap(_.symbol.asTerm), name == nme.CONSTRUCTOR) val resType = ctx.effectiveResultType(sym, typeParams, tpt.tpe) - sym.info = ctx.methodType(typeParams, valueParamss, resType) + sym.info = ctx.termLambda(typeParams, valueParamss, resType, sym) DefDef(tparams, vparamss, tpt) case VALDEF => val tpt = readTpt()(localCtx) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 79c00e0f9f92..f922b4bebbe6 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -167,7 +167,7 @@ class PlainPrinter(_ctx: Context) extends Printer { "" case NoPrefix => "" - case tp: MethodType => + case tp: TermLambda => changePrec(GlobalPrec) { ("(" + (if (tp.isErasedMethod) "erased " else "") + (if (tp.isImplicitMethod) "implicit " else "") diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 3d9c9bc13bca..0d952786e967 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -208,12 +208,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { ": " ~ toText(tp.memberProto) ~ " }" case tp: ViewProto => return toText(tp.argType) ~ " ?=>? " ~ toText(tp.resultType) - case tp @ FunProto(args, resultType, _) => - val argsText = args match { + case tp: FunProto => + val argsText = tp.args match { case dummyTreeOfType(tp) :: Nil if !(tp isRef defn.NullClass) => "null: " ~ toText(tp) - case _ => toTextGlobal(args, ", ") + case _ => toTextGlobal(tp.args, ", ") } - return "FunProto(" ~ argsText ~ "):" ~ toText(resultType) + return "FunProto(" ~ argsText ~ "):" ~ toText(tp.resultType) case tp: IgnoredProto => return "?" case tp @ PolyProto(targs, resType) => @@ -593,7 +593,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { if (sym.privateWithin.exists) sym.privateWithin.asType.name else tpnme.EMPTY, sym.annotations map (_.tree)) - protected def optAscription[T >: Untyped](tpt: Tree[T]) = optText(tpt)(": " ~ _) + protected def optAscription[T >: Untyped](tpt: Tree[T])(implicit ctx: Context) = + if (tpt.typeOpt.isInstanceOf[TypeBounds]) toText(tpt) + else optText(tpt)(": " ~ _) private def idText(tree: untpd.Tree): Text = { if ((ctx.settings.uniqid.value || Printer.debugPrintUnique) && tree.hasType && tree.symbol.exists) s"#${tree.symbol.id}" else "" @@ -641,7 +643,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { protected def defDefToText[T >: Untyped](tree: DefDef[T]): Text = { import untpd.{modsDeco => _, _} dclTextOr(tree) { - val prefix = modText(tree.mods, tree.symbol, keywordStr("def")) ~~ valDefText(nameIdText(tree)) + val keyword = if (tree.symbol.isType) "type" else "def" + val prefix = modText(tree.mods, tree.symbol, keywordStr(keyword)) ~~ valDefText(nameIdText(tree)) withEnclosingDef(tree) { addVparamssText(prefix ~ tparamsText(tree.tparams), tree.vparamss) ~ optAscription(tree.tpt) ~ optText(tree.rhs)(" = " ~ _) diff --git a/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala b/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala index 4adf5405590a..a06b14060fb5 100644 --- a/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala @@ -23,7 +23,8 @@ class StoreReporter(outer: Reporter) extends Reporter { protected[this] var infos: mutable.ListBuffer[MessageContainer] = null def doReport(m: MessageContainer)(implicit ctx: Context): Unit = { - typr.println(s">>>> StoredError: ${m.message}") // !!! DEBUG + typr.println(s">>>> StoredError: ${m.message}") + // new AssertionError().printStackTrace() // DEBUG if (infos == null) infos = new mutable.ListBuffer infos += m } diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index d401ce5aa3c4..38bdddb94b38 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -540,35 +540,37 @@ object Erasure { * 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) = + if (sym.isType) EmptyTree + 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/ResolveSuper.scala b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala index 940ce9199a08..6fdb531758e9 100644 --- a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala +++ b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala @@ -80,13 +80,13 @@ class ResolveSuper extends MiniPhase with IdentityDenotTransformer { thisPhase = } override def transformDefDef(ddef: DefDef)(implicit ctx: Context) = { - val meth = ddef.symbol.asTerm + val meth = ddef.symbol if (meth.isSuperAccessor && !meth.is(Deferred)) { assert(ddef.rhs.isEmpty) val cls = meth.owner.asClass val ops = new MixinOps(cls, thisPhase) import ops._ - polyDefDef(meth, forwarder(rebindSuper(cls, meth))) + polyDefDef(meth.asTerm, forwarder(rebindSuper(cls, meth))) } else ddef } diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index e1c36b9395c3..85fbc123f785 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -300,10 +300,11 @@ class TreeChecker extends Phase with SymTransformer { tree } - /** Check that all methods have MethodicType */ - def isMethodType(pt: Type)(implicit ctx: Context): Boolean = pt match { - case at: AnnotatedType => isMethodType(at.parent) - case _: MethodicType => true // MethodType, ExprType, PolyType + /** Check that all methods have LambdaType or ExprType */ + def isDefType(tp: Type)(implicit ctx: Context): Boolean = tp match { + case _: LambdaType | _: ExprType => true + case tp: TypeBounds => isDefType(tp.hi) + case tp: AnnotatedType => isDefType(tp.parent) case _ => false } @@ -406,7 +407,7 @@ class TreeChecker extends Phase with SymTransformer { }) val tpdTree = super.typedDefDef(ddef, sym) - assert(isMethodType(sym.info), i"wrong type, expect a method type for ${sym.fullName}, but found: ${sym.info}") + assert(isDefType(sym.info), i"wrong type, expect a method type for ${sym.fullName}, but found: ${sym.info}") tpdTree } } diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index ccec09ce8efa..99e0a3e23c6c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -143,7 +143,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * @param args the arguments of the application * @param resultType the expected result type of the application */ - abstract class Application[Arg](methRef: TermRef, funType: Type, args: List[Arg], resultType: Type)(implicit ctx: Context) { + abstract class Application[Arg](methRef: NamedType, funType: Type, args: List[Arg], resultType: Type)(implicit ctx: Context) { /** The type of typed arguments: either tpd.Tree or Type */ type TypedArg @@ -211,15 +211,15 @@ trait Applications extends Compatibility { self: Typer with Dynamic => /** The function's type after widening and instantiating polytypes * with TypeParamRefs in constraint set */ - lazy val methType: Type = liftedFunType.widen match { - case funType: MethodType => funType - case funType: PolyType => constrained(funType).resultType + lazy val methType: Type = liftedFunType.widen.toLambda match { + case funType: TermLambda => funType + case funType: TypeLambda => constrained(funType).resultType case tp => tp //was: funType } def reqiredArgNum(tp: Type): Int = tp.widen match { - case funType: MethodType => funType.paramInfos.size - case funType: PolyType => reqiredArgNum(funType.resultType) + case funType: TermLambda => funType.paramInfos.size + case funType: TypeLambda => reqiredArgNum(funType.resultType) case tp => args.size } @@ -240,7 +240,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => args protected def init() = methType match { - case methType: MethodType => + case methType: TermLambda => // apply the result type constraint, unless method type is dependent val resultApprox = resultTypeApprox(methType) if (!constrainResult(methRef.symbol, resultApprox, resultType)) @@ -255,13 +255,13 @@ trait Applications extends Compatibility { self: Typer with Dynamic => matchArgs(orderedArgs, methType.paramInfos, 0) case _ => if (methType.isError) ok = false - else fail(s"$methString does not take parameters") + else fail(s"$methString does not take parameters?") } /** The application was successful */ def success = ok - protected def methodType = methType.asInstanceOf[MethodType] + protected def lambda = methType.asInstanceOf[TermLambda] private def methString: String = i"${methRef.symbol}: ${methType.show}" /** Re-order arguments to correctly align named arguments */ @@ -292,7 +292,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => genericEmptyTree :: handleNamed(pnames.tail, args, nameToArg, toDrop) else { // name not (or no longer) available for named arg def msg = - if (methodType.paramNames contains aname) + if (lambda.paramNames contains aname) s"parameter $aname of $methString is already instantiated" else s"$methString does not have a parameter $aname" @@ -317,7 +317,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => case Nil => Nil } - handlePositional(methodType.paramNames, args) + handlePositional(lambda.paramNames, args) } /** Splice new method reference into existing application */ @@ -432,13 +432,13 @@ trait Applications extends Compatibility { self: Typer with Dynamic => */ def addTyped(arg: Arg, formal: Type): Type => Type = { addArg(typedArg(arg, formal), formal) - if (methodType.isParamDependent) - safeSubstParam(_, methodType.paramRefs(n), typeOfArg(arg)) + if (lambda.isParamDependent) + safeSubstParam(_, lambda.paramRefs(n), typeOfArg(arg)) else identity } def missingArg(n: Int): Unit = { - val pname = methodType.paramNames(n) + val pname = lambda.paramNames(n) fail( if (pname.firstPart contains '$') s"not enough arguments for $methString" else s"missing argument for parameter $pname of $methString") @@ -488,7 +488,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * in a "can/cannot apply" answer, without needing to construct trees or * issue error messages. */ - abstract class TestApplication[Arg](methRef: TermRef, funType: Type, args: List[Arg], resultType: Type)(implicit ctx: Context) + abstract class TestApplication[Arg](methRef: NamedType, funType: Type, args: List[Arg], resultType: Type)(implicit ctx: Context) extends Application[Arg](methRef, funType, args, resultType) { type TypedArg = Arg type Result = Unit @@ -524,7 +524,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => /** Subclass of Application for applicability tests with type arguments and value * argument trees. */ - class ApplicableToTrees(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context) + class ApplicableToTrees(methRef: NamedType, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context) extends TestApplication(methRef, methRef.widen.appliedTo(targs), args, resultType) { def argType(arg: Tree, formal: Type): Type = normalize(arg.tpe, formal) def treeToArg(arg: Tree): Tree = arg @@ -536,12 +536,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic => /** Subclass of Application for applicability tests with type arguments and value * argument trees. */ - class ApplicableToTreesDirectly(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context) extends ApplicableToTrees(methRef, targs, args, resultType)(ctx) { + class ApplicableToTreesDirectly(methRef: NamedType, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context) extends ApplicableToTrees(methRef, targs, args, resultType)(ctx) { override def argOK(arg: TypedArg, formal: Type) = argType(arg, formal) <:< formal } /** Subclass of Application for applicability tests with value argument types. */ - class ApplicableToTypes(methRef: TermRef, args: List[Type], resultType: Type)(implicit ctx: Context) + class ApplicableToTypes(methRef: NamedType, args: List[Type], resultType: Type)(implicit ctx: Context) extends TestApplication(methRef, methRef, args, resultType) { def argType(arg: Type, formal: Type): Type = arg def treeToArg(arg: Tree): Type = arg.tpe @@ -554,7 +554,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * types of arguments are either known or unknown. */ abstract class TypedApply[T >: Untyped]( - app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[Trees.Tree[T]], resultType: Type)(implicit ctx: Context) + app: untpd.Apply, fun: Tree, methRef: NamedType, args: List[Trees.Tree[T]], resultType: Type)(implicit ctx: Context) extends Application(methRef, fun.tpe, args, resultType) { type TypedArg = Tree def isVarArg(arg: Trees.Tree[T]): Boolean = untpd.isWildcardStarArg(arg) @@ -658,7 +658,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => } /** Subclass of Application for type checking an Apply node with untyped arguments. */ - class ApplyToUntyped(app: untpd.Apply, fun: Tree, methRef: TermRef, proto: FunProto, resultType: Type)(implicit ctx: Context) + class ApplyToUntyped(app: untpd.Apply, fun: Tree, methRef: NamedType, proto: FunProto, resultType: Type)(implicit ctx: Context) extends TypedApply(app, fun, methRef, proto.args, resultType) { def typedArg(arg: untpd.Tree, formal: Type): TypedArg = proto.typedArg(arg, formal.widenExpr) def treeToArg(arg: Tree): untpd.Tree = untpd.TypedSplice(arg) @@ -666,7 +666,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => } /** Subclass of Application for type checking an Apply node with typed arguments. */ - class ApplyToTyped(app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[Tree], resultType: Type)(implicit ctx: Context) + class ApplyToTyped(app: untpd.Apply, fun: Tree, methRef: NamedType, args: List[Tree], resultType: Type)(implicit ctx: Context) extends TypedApply[Type](app, fun, methRef, args, resultType) { // Dotty deviation: Dotc infers Untyped for the supercall. This seems to be according to the rules // (of both Scala and Dotty). Untyped is legal, and a subtype of Typed, whereas TypeApply @@ -681,7 +681,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * otherwise the current context. */ def argCtx(app: untpd.Tree)(implicit ctx: Context): Context = - if (untpd.isSelfConstrCall(app)) ctx.thisCallArgContext else ctx + (if (untpd.isSelfConstrCall(app)) ctx.thisCallArgContext else ctx).retractMode(Mode.Type) /** Typecheck application. Result could be an `Apply` node, * or, if application is an operator assignment, also an `Assign` or @@ -714,7 +714,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => /** Type application where arguments come from prototype, and no implicits are inserted */ def simpleApply(fun1: Tree, proto: FunProto)(implicit ctx: Context): Tree = methPart(fun1).tpe match { - case funRef: TermRef => + case funRef: NamedType => val app = if (proto.allArgTypesAreCurrent()) new ApplyToTyped(tree, fun1, funRef, proto.typedArgs, pt) @@ -1362,9 +1362,9 @@ trait Applications extends Compatibility { self: Typer with Dynamic => alts filter (isApplicable(_, argTypes, resultType)) val candidates = pt match { - case pt @ FunProto(args, resultType, _) => - val numArgs = args.length - val normArgs = args.mapConserve { + case pt: FunProto => + val numArgs = pt.args.length + val normArgs = pt.args.mapConserve { case Block(Nil, expr) => expr case x => x } @@ -1388,8 +1388,8 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def narrowByShapes(alts: List[TermRef]): List[TermRef] = { if (normArgs exists untpd.isFunctionWithUnknownParamType) - if (hasNamedArg(args)) narrowByTrees(alts, args map treeShape, resultType) - else narrowByTypes(alts, normArgs map typeShape, resultType) + if (hasNamedArg(normArgs)) narrowByTrees(alts, normArgs map treeShape, pt.resultType) + else narrowByTypes(alts, normArgs map typeShape, pt.resultType) else alts } @@ -1415,7 +1415,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => if (isDetermined(alts2)) alts2 else { pretypeArgs(alts2, pt) - narrowByTrees(alts2, pt.typedArgs, resultType) + narrowByTrees(alts2, pt.typedArgs, pt.resultType) } } diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 325e478545e7..1792022d44ef 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -385,7 +385,7 @@ object Inliner { ctx.isAfterTyper || ctx.reporter.hasErrors - hasBodyToInline(meth.symbol) && !suppressInline + hasBodyToInline(meth) && !suppressInline } /** Is `meth` a transparent method that should be inlined in this context? */ @@ -485,7 +485,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { /** A buffer for bindings that define proxies for actual arguments */ val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] - computeParamBindings(meth.info, targs, argss) + computeParamBindings(meth.info.hiBound, targs, argss) private def newSym(name: Name, flags: FlagSet, info: Type): Symbol = ctx.newSymbol(ctx.owner, name, flags, info, coord = call.pos) @@ -520,12 +520,12 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { * proxies to this-references. */ private def computeParamBindings(tp: Type, targs: List[Tree], argss: List[List[Tree]]): Unit = tp match { - case tp: PolyType => + case tp: TypeLambda => (tp.paramNames, targs).zipped.foreach { (name, arg) => paramBinding(name) = arg.tpe.stripTypeVar } computeParamBindings(tp.resultType, Nil, argss) - case tp: MethodType => + case tp: TermLambda => (tp.paramNames, tp.paramInfos, argss.head).zipped.foreach { (name, paramtp, arg) => paramBinding(name) = arg.tpe.dealias match { case _: SingletonType if isIdempotentExpr(arg) => arg.tpe @@ -834,7 +834,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = { - val cond1 = typed(tree.cond, defn.BooleanType) + val cond1 = typedExpr(tree.cond, defn.BooleanType) cond1.tpe.widenTermRefExpr match { case ConstantType(Constant(condVal: Boolean)) => var selected = typed(if (condVal) tree.thenp else tree.elsep, pt) @@ -862,8 +862,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { */ 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 => + ddef.tpe.widen.toLambda match { + case mt: TermLambda 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 { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 6f7805f12b8a..310f24f51f0f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -123,7 +123,7 @@ trait NamerContextOps { this: Context => else given /** if isConstructor, make sure it has one non-implicit parameter list */ - def normalizeIfConstructor(termParamss: List[List[Symbol]], isConstructor: Boolean) = + def normalizeIfConstructor(termParamss: List[List[TermSymbol]], isConstructor: Boolean) = if (isConstructor && (termParamss.isEmpty || termParamss.head.nonEmpty && (termParamss.head.head is Implicit))) Nil :: termParamss @@ -131,19 +131,23 @@ trait NamerContextOps { this: Context => termParamss /** The method type corresponding to given parameters and result type */ - def methodType(typeParams: List[Symbol], valueParamss: List[List[Symbol]], resultType: Type, isJava: Boolean = false)(implicit ctx: Context): Type = { + def termLambda(typeParams: List[TypeSymbol], valueParamss: List[List[TermSymbol]], resultType: Type, sym: Symbol)(implicit ctx: Context): Type = { val monotpe = (valueParamss :\ resultType) { (params, resultType) => - val (isImplicit, isErased) = - if (params.isEmpty) (false, false) - else (params.head is Implicit, params.head is Erased) - val make = MethodType.maker(isJava, isImplicit, isErased) - if (isJava) - for (param <- params) - if (param.info.isDirectRef(defn.ObjectClass)) param.info = defn.AnyType - make.fromSymbols(params.asInstanceOf[List[TermSymbol]], resultType) + if (sym.isTerm) { + val isJava = sym.flagsUNSAFE.is(JavaDefined) + val (isImplicit, isErased) = + if (params.isEmpty) (false, false) + else (params.head is Implicit, params.head is Erased) + val make = MethodType.maker(isJava, isImplicit, isErased) + if (isJava) + for (param <- params) + if (param.info.isDirectRef(defn.ObjectClass)) param.info = defn.AnyType + make.fromSymbols(params, resultType) + } + else HKTermLambda.fromParams(params, resultType) } - if (typeParams.nonEmpty) PolyType.fromParams(typeParams.asInstanceOf[List[TypeSymbol]], monotpe) + if (typeParams.nonEmpty) PolyType.fromParams(typeParams, monotpe) else if (valueParamss.isEmpty) ExprType(monotpe) else monotpe } @@ -341,7 +345,10 @@ class Namer { typer: Typer => val flags = checkFlags(tree.mods.flags) val isDeferred = lacksDefinition(tree) val deferred = if (isDeferred) Deferred else EmptyFlags - val method = if (tree.isInstanceOf[DefDef]) Method else EmptyFlags + val method = + if (!tree.isInstanceOf[DefDef]) EmptyFlags + else if (name.isTypeName) TypeMethod | Transparent + else Method // to complete a constructor, move one context further out -- this // is the context enclosing the class. Note that the context in which a @@ -1012,6 +1019,9 @@ class Namer { typer: Typer => def typedAheadExpr(tree: Tree, pt: Type = WildcardType)(implicit ctx: Context): tpd.Tree = typedAheadImpl(tree, typer.typed(_, pt)(ctx retractMode Mode.PatternOrTypeBits)) + def typedAheadRHS(rhs: Tree, pt: Type = WildcardType, sym: Symbol)(implicit ctx: Context): tpd.Tree = + if (sym.isTerm) typedAheadExpr(rhs, pt) else typedAheadType(rhs, pt) + def typedAheadAnnotation(tree: Tree)(implicit ctx: Context): tpd.Tree = typedAheadExpr(tree, defn.AnnotationType) @@ -1056,7 +1066,7 @@ class Namer { typer: Typer => * NoType if neither case holds. */ val inherited = - if (sym.owner.isTerm) NoType + if (sym.owner.isTerm || sym.isType) NoType else { // TODO: Look only at member of supertype instead? lazy val schema = paramFn(WildcardType) @@ -1093,7 +1103,7 @@ class Namer { typer: Typer => * the corresponding parameter where bound parameters are replaced by * Wildcards. */ - def rhsProto = sym.asTerm.name collect { + def rhsProto = sym.name collect { case DefaultGetterName(original, idx) => val meth: Denotation = if (original.isConstructorName && (sym.owner is ModuleClass)) @@ -1116,13 +1126,14 @@ 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 isInlineVal = + sym.isTerm && sym.is(FinalOrInlineOrTransparent, 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 // instead of widening to the underlying module class types. def widenRhs(tp: Type): Type = tp.widenTermRefExpr match { - case ctp: ConstantType if isInline => ctp + case ctp: ConstantType if isInlineVal => ctp case ref: TypeRef if ref.symbol.is(ModuleClass) => tp case _ => tp.widen.widenUnion } @@ -1133,7 +1144,9 @@ class Namer { typer: Typer => var rhsCtx = ctx.addMode(Mode.InferringReturnType) if (sym.isTransparentMethod) rhsCtx = rhsCtx.addMode(Mode.TransparentBody) - def rhsType = typedAheadExpr(mdef.rhs, inherited orElse rhsProto)(rhsCtx).tpe + def rhsType = + if (sym.isTerm) typedAheadExpr(mdef.rhs, inherited orElse rhsProto)(rhsCtx).tpe + else typedAheadType(mdef.rhs)(rhsCtx).tpe // Approximate a type `tp` with a type that does not contain skolem types. val deskolemize = new ApproximatingTypeMap { @@ -1172,7 +1185,8 @@ class Namer { typer: Typer => case _: untpd.DerivedTypeTree => WildcardType case TypeTree() => - checkMembersOK(inferredType, mdef.pos) + if (sym.isType) TypeBounds.empty + else checkMembersOK(inferredType, mdef.pos) case DependentTypeTree(tpFun) => tpFun(paramss.head) case TypedSplice(tpt: TypeTree) if !isFullyDefined(tpt.tpe, ForceDegree.none) => @@ -1227,11 +1241,13 @@ class Namer { typer: Typer => for (tparam <- tparams) typedAheadExpr(tparam) vparamss foreach completeParams - def typeParams = tparams map symbolOfTree - val termParamss = ctx.normalizeIfConstructor(vparamss.nestedMap(symbolOfTree), isConstructor) + def typeParams = tparams.map(symbolOfTree(_).asType) + val termParamss = ctx.normalizeIfConstructor( + vparamss.nestedMap(symbolOfTree(_).asTerm), isConstructor) def wrapMethType(restpe: Type): Type = { + if (sym.isType) assert(restpe.isInstanceOf[TypeBounds], restpe) instantiateDependent(restpe, typeParams, termParamss) - ctx.methodType(tparams map symbolOfTree, termParamss, restpe, isJava = ddef.mods is JavaDefined) + ctx.termLambda(typeParams, termParamss, restpe, sym) } if (isConstructor) { // set result type tree to unit, but take the current class as result type of the symbol diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index f38fb6db6e37..a40151e6297c 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -205,8 +205,9 @@ object ProtoTypes { * * [](args): resultType */ - case class FunProto(args: List[untpd.Tree], resType: Type, typer: Typer)(implicit ctx: Context) + class FunProto(val args: List[untpd.Tree], val resType: Type, val typer: Typer)(implicit ctx: Context) extends UncachedGroundType with ApplyingProto { + private[this] var myTypedArgs: List[Tree] = Nil override def resultType(implicit ctx: Context) = resType @@ -306,7 +307,7 @@ object ProtoTypes { case pt: FunProto => pt case _ => - myTupled = new FunProto(untpd.Tuple(args) :: Nil, resultType, typer) + myTupled = FunProto(untpd.Tuple(args) :: Nil, resultType, typer) tupled } @@ -340,6 +341,11 @@ object ProtoTypes { override def deepenProto(implicit ctx: Context) = derivedFunProto(args, resultType.deepenProto, typer) } + object FunProto { + def apply(args: List[untpd.Tree], resType: Type, typer: Typer)(implicit ctx: Context) = + new FunProto(args, resType, typer)(ctx.retractMode(Mode.Type)) + } + /** A prototype for expressions that appear in function position * @@ -383,7 +389,7 @@ object ProtoTypes { } class UnapplyFunProto(argType: Type, typer: Typer)(implicit ctx: Context) extends FunProto( - untpd.TypedSplice(dummyTreeOfType(argType))(ctx) :: Nil, WildcardType, typer) + untpd.TypedSplice(dummyTreeOfType(argType))(ctx) :: Nil, WildcardType, typer)(ctx) /** A prototype for expressions [] that are type-parameterized: * @@ -484,7 +490,7 @@ object ProtoTypes { /** The result type of `mt`, where all references to parameters of `mt` are * replaced by either wildcards (if typevarsMissContext) or TypeParamRefs. */ - def resultTypeApprox(mt: MethodType)(implicit ctx: Context): Type = + def resultTypeApprox(mt: TermLambda)(implicit ctx: Context): Type = if (mt.isResultDependent) { def replacement(tp: Type) = if (ctx.mode.is(Mode.TypevarsMissContext) || diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index afc4e83439e1..ce468f56bf98 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -362,8 +362,8 @@ trait TypeAssigner { } def assignType(tree: untpd.Apply, fn: Tree, args: List[Tree])(implicit ctx: Context) = { - val ownType = fn.tpe.widen match { - case fntpe: MethodType => + val ownType = fn.tpe.widen.toLambda match { + case fntpe: TermLambda => if (sameLength(fntpe.paramInfos, args) || ctx.phase.prev.relaxedTyping) if (fntpe.isResultDependent) safeSubstParams(fntpe.resultType, fntpe.paramRefs, args.tpes) else fntpe.resultType diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 28b59512de73..213e8f2b476c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -663,7 +663,7 @@ class Typer extends Namer def typedBlock(tree: untpd.Block, pt: Type)(implicit ctx: Context) = track("typedBlock") { val (exprCtx, stats1) = typedBlockStats(tree.stats) - val expr1 = typedExpr(tree.expr, pt.notApplied)(exprCtx) + val expr1 = typed(tree.expr, pt.notApplied)(exprCtx) ensureNoLocalRefs( cpy.Block(tree)(stats1, expr1).withType(expr1.tpe), pt, localSyms(stats1)) } @@ -707,7 +707,7 @@ class Typer extends Namer } def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context): Tree = track("typedIf") { - val cond1 = typed(tree.cond, defn.BooleanType) + val cond1 = typedExpr(tree.cond, defn.BooleanType) val thenp2 :: elsep2 :: Nil = harmonic(harmonize) { val thenp1 = typed(tree.thenp, pt.notApplied) val elsep1 = typed(tree.elsep orElse (untpd.unitLiteral withPos tree.pos), pt.notApplied) @@ -1407,7 +1407,7 @@ class Typer extends Namer } } - def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context) = track("typedDefDef") { + def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context): Tree = track("typedDefDef") { val DefDef(name, tparams, vparamss, tpt, _) = ddef completeAnnotations(ddef, sym) val tparams1 = tparams mapconserve (typed(_).asInstanceOf[TypeDef]) @@ -1429,7 +1429,9 @@ class Typer extends Namer rhsCtx.gadt.setBounds(tdef.symbol, TypeAlias(tparam.typeRef))) } if (sym.isTransparentMethod) rhsCtx = rhsCtx.addMode(Mode.TransparentBody) - val rhs1 = normalizeErasedRhs(typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx), sym) + val rhs1 = + if (sym.isTerm) normalizeErasedRhs(typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx), sym) + else typedType(ddef.rhs, tpt1.tpe)(rhsCtx) // Overwrite inline body to make sure it is not evaluated twice if (sym.isInlineableMethod) Inliner.registerInlineInfo(sym, ddef.rhs, _ => rhs1) @@ -2029,8 +2031,9 @@ class Typer extends Namer tryInsertImplicitOnQualifier(tree, pt, locked).getOrElse(fallBack) pt match { - case pt @ FunProto(Nil, _, _) - if tree.symbol.allOverriddenSymbols.exists(_.info.isNullaryMethod) && + case pt: FunProto + if pt.args.isEmpty && + tree.symbol.allOverriddenSymbols.exists(_.info.isNullaryMethod) && tree.getAttachment(DroppedEmptyArgs).isEmpty => tree.putAttachment(DroppedEmptyArgs, ()) pt.markAsDropped() @@ -2161,6 +2164,8 @@ class Typer extends Namer adapt(tree, pt.tupled, locked) else tree + case wtp: TypeRef if wtp.underlying.hiBound.isInstanceOf[HKLambda] => + tree case _ => tryInsertApplyOrImplicit(tree, pt, locked) { errorTree(tree, MethodDoesNotTakeParameters(tree)) } @@ -2496,7 +2501,20 @@ class Typer extends Namer tree.tpe.EtaExpand(tp.typeParamSymbols) tree.withType(tp1) } - if ((ctx.mode is Mode.Pattern) || tree1.tpe <:< pt) tree1 + val isCompatible = + if (ctx.mode `is` Mode.Pattern) true + else pt match { + case pt: TypeBounds if ctx.mode `is` Mode.Type => pt `contains` tree1.tpe + case _ => tree1.tpe <:< pt + } + if (Inliner.isInlineable(tree.symbol)) + readaptSimplified { + Inliner.inlineCall(tree, pt) match { + case inlined: Inlined => TypeTree(inlined.tpe).withPos(tree.pos) + case err => err + } + } + else if (isCompatible) tree1 else err.typeMismatch(tree1, pt) } diff --git a/tests/run/typelevel2.scala b/tests/run/typelevel2.scala new file mode 100644 index 000000000000..db203227b545 --- /dev/null +++ b/tests/run/typelevel2.scala @@ -0,0 +1,37 @@ +trait Nat { + def toInt: Int +} + +case object Z extends Nat { + transparent def toInt = 0 +} + +case class S[N <: Nat](n: N) extends Nat { + transparent def toInt = n.toInt + 1 +} + +object Test extends App { + type Z = Z.type + + type IntOrString(x: Boolean) = if x then Int else String + + val x: IntOrString(true) = 1 + val x1: Int = x + val y: IntOrString(false) = "" + val y1: String = y + + type ToNat(n: Int) <: Nat = + if n == 0 then Z + else S[ToNat(n - 1)] + + val n0: ToNat(0) = Z + val n1: ToNat(1) = S(Z) + val n3: ToNat(3) = S(S(S(Z))) + val i0: 0 = n0.toInt + val i1: 1 = n1.toInt + val i3: 3 = n3.toInt + + def ii: Int = 2 + def nn: ToNat(ii) = S(S(Z)) // no expansion possible, since `ii` is of type `Int`. + val ii1: Int = nn.toInt +} \ No newline at end of file From 0ec4d9811b5d0084bb20180651b4c661b745f1b5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 Jun 2018 15:06:08 +0200 Subject: [PATCH 51/64] Implement pickling and unpickling of computed types --- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 6 +++ .../tools/dotc/core/tasty/TastyFormat.scala | 9 +++- .../tools/dotc/core/tasty/TreePickler.scala | 3 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 44 ++++++++++++------- .../src/dotty/tools/dotc/typer/Namer.scala | 4 +- 5 files changed, 46 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 8b454674b547..65f9ed7d7b21 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -291,6 +291,12 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped] case _ => tree } + def isBounds(tree: Tree)(implicit ctx: Context) = tree match { + case tree: TypeBoundsTree => true + case TypedSplice(tree1) => tree1.tpe.isInstanceOf[TypeBounds] + case _ => false + } + /** True iff definition is a val or def with no right-hand-side, or it * is an abstract typoe declaration */ diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index de1e04fcadf2..dd56815f8727 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -58,7 +58,11 @@ Standard-Section: "ASTs" TopLevelStat* VALDEF Length NameRef type_Term rhs_Term? Modifier* DEFDEF Length NameRef TypeParam* Params* returnType_Term rhs_Term? Modifier* - TYPEDEF Length NameRef (type_Term | Template) Modifier* + TYPEDEF Length NameRef + ( type_Term + | Template + | TypeParam* Params* returnType_Term rhs_Term + ) Modifier* OBJECTDEF Length NameRef Template Modifier* IMPORT Length qual_Term Selector* Selector = IMPORTED name_NameRef @@ -510,7 +514,8 @@ object TastyFormat { | ANDtpt | ORtpt | BYNAMEtpt - | BIND => true + | BIND + | APPLY => true case _ => false } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index b1e6b87e9a78..0b6fd4392732 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -469,7 +469,8 @@ class TreePickler(pickler: TastyPickler) { withLength { pickleParams(vparams) } } } - pickleDef(DEFDEF, tree.symbol, tree.tpt, tree.rhs, pickleAllParams) + val tag = if (tree.symbol.isTerm) DEFDEF else TYPEDEF + pickleDef(tag, tree.symbol, tree.tpt, tree.rhs, pickleAllParams) case tree: TypeDef => pickleDef(TYPEDEF, tree.symbol, tree.rhs) case tree: Template => diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 714a4f5be246..4a1707a69002 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -438,6 +438,7 @@ class TreeUnpickler(reader: TastyReader, var flags = givenFlags if (lacksDefinition && tag != PARAM) flags |= Deferred if (tag == DEFDEF) flags |= Method + if (tag == TYPEDEF && givenFlags.is(Transparent)) flags |= TypeMethod if (givenFlags is Module) flags = flags | (if (tag == VALDEF) ModuleValCreationFlags else ModuleClassCreationFlags) if (ctx.owner.isClass) { @@ -754,9 +755,9 @@ class TreeUnpickler(reader: TastyReader, ta.assignType(untpd.ValDef(sym.name.asTermName, tpt, readRhs(localCtx)), sym) def DefDef(tparams: List[TypeDef], vparamss: List[List[ValDef]], tpt: Tree) = - ta.assignType( - untpd.DefDef(sym.name.asTermName, tparams, vparamss, tpt, readRhs(localCtx)), - sym) + ta.assignType( + untpd.DefDef(sym.name, tparams, vparamss, tpt, readRhs(localCtx)), + sym) def TypeDef(rhs: Tree) = ta.assignType(untpd.TypeDef(sym.name.asTypeName, rhs), sym) @@ -764,18 +765,23 @@ class TreeUnpickler(reader: TastyReader, def ta = ctx.typeAssigner val name = readName() + + def readDefDef() = { + val tparams = readParams[TypeDef](TYPEPARAM)(localCtx) + val vparamss = readParamss(localCtx) + val tpt = readTpt()(localCtx) + val typeParams = tparams.map(_.symbol.asType) + val valueParamss = ctx.normalizeIfConstructor( + vparamss.nestedMap(_.symbol.asTerm), name == nme.CONSTRUCTOR) + val resType = ctx.effectiveResultType(sym, typeParams, tpt.tpe) + sym.info = ctx.lambdaType(typeParams, valueParamss, resType, sym) + DefDef(tparams, vparamss, tpt) + } + pickling.println(s"reading def of $name at $start") val tree: MemberDef = tag match { case DEFDEF => - val tparams = readParams[TypeDef](TYPEPARAM)(localCtx) - val vparamss = readParamss(localCtx) - val tpt = readTpt()(localCtx) - val typeParams = tparams.map(_.symbol.asType) - val valueParamss = ctx.normalizeIfConstructor( - vparamss.nestedMap(_.symbol.asTerm), name == nme.CONSTRUCTOR) - val resType = ctx.effectiveResultType(sym, typeParams, tpt.tpe) - sym.info = ctx.termLambda(typeParams, valueParamss, resType, sym) - DefDef(tparams, vparamss, tpt) + readDefDef() case VALDEF => val tpt = readTpt()(localCtx) sym.info = tpt.tpe @@ -795,7 +801,10 @@ class TreeUnpickler(reader: TastyReader, else sym.registerCompanionMethod(nme.COMPANION_MODULE_METHOD, companion) } TypeDef(readTemplate(localCtx)) - } else { + } + else if (sym.is(Transparent)) + readDefDef() + else { sym.info = TypeBounds.empty // needed to avoid cyclic references when unpicklin rhs, see i3816.scala sym.setFlag(Provisional) val rhs = readTpt()(localCtx) @@ -1337,8 +1346,13 @@ class TreeUnpickler(reader: TastyReader, case ALTERNATIVE => untpd.Alternative(until(end)(readUntyped())) case DEFDEF => - untpd.DefDef(readName(), readParams[TypeDef](TYPEPARAM), readParamss(), readUntyped(), readRhs()) - .withMods(readMods()) + var name = readName() + val tparams = readParams[TypeDef](TYPEPARAM) + val vparamss = readParamss() + val tpt = readUntyped() + untpd.DefDef( + if (untpd.isBounds(tpt)) name.toTypeName else name, + tparams, vparamss, tpt, readRhs()).withMods(readMods()) case VALDEF | PARAM => untpd.ValDef(readName(), readUntyped(), readRhs()) .withMods(readMods()) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 310f24f51f0f..5b790435cb47 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -131,7 +131,7 @@ trait NamerContextOps { this: Context => termParamss /** The method type corresponding to given parameters and result type */ - def termLambda(typeParams: List[TypeSymbol], valueParamss: List[List[TermSymbol]], resultType: Type, sym: Symbol)(implicit ctx: Context): Type = { + def lambdaType(typeParams: List[TypeSymbol], valueParamss: List[List[TermSymbol]], resultType: Type, sym: Symbol)(implicit ctx: Context): Type = { val monotpe = (valueParamss :\ resultType) { (params, resultType) => if (sym.isTerm) { @@ -1247,7 +1247,7 @@ class Namer { typer: Typer => def wrapMethType(restpe: Type): Type = { if (sym.isType) assert(restpe.isInstanceOf[TypeBounds], restpe) instantiateDependent(restpe, typeParams, termParamss) - ctx.termLambda(typeParams, termParamss, restpe, sym) + ctx.lambdaType(typeParams, termParamss, restpe, sym) } if (isConstructor) { // set result type tree to unit, but take the current class as result type of the symbol From 486f412cf0d938b4cb7d9a1d0ae5f5f285deff68 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 Jun 2018 15:44:28 +0200 Subject: [PATCH 52/64] Delete ErrorMessageTest I must say, these error message tests are probably the biggest productivity killer in working with dotty. Why do we have them? What do they accomplish? They certainly throw lost of spammers in the works. --- .../tools/dotc/reporting/ErrorMessagesTests.scala | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index 0f7605966b98..bd801311acbe 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -988,20 +988,6 @@ class ErrorMessagesTests extends ErrorMessagesTest { assertEquals(method.show, "method foo") } - @Test def expectedTypeBoundOrEquals = - checkMessagesAfter(FrontEnd.name) { - """object typedef { - | type asd > Seq - |} - """.stripMargin - }.expect { (ictx, messages) => - implicit val ctx: Context = ictx - - assertMessageCount(1, messages) - val ExpectedTypeBoundOrEquals(found) :: Nil = messages - assertEquals(Tokens.IDENTIFIER, found) - } - @Test def classAndCompanionNameClash = checkMessagesAfter(RefChecks.name) { """ From 55bac277d99b9433fd650b94229d00be873f423c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 Jun 2018 17:44:29 +0200 Subject: [PATCH 53/64] Fixes to allow for generic transparent type devfinitions --- compiler/src/dotty/tools/dotc/ast/TreeInfo.scala | 2 ++ compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- .../src/dotty/tools/dotc/typer/Applications.scala | 4 ++-- compiler/src/dotty/tools/dotc/typer/Namer.scala | 10 +++++++--- compiler/src/dotty/tools/dotc/typer/Typer.scala | 11 +++++------ tests/run/typelevel2.scala | 11 +++++++++++ 6 files changed, 28 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 65f9ed7d7b21..6a7a3d1fe689 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -578,6 +578,8 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => loop(fn, targss, args :: argss) case TypeApply(fn, targs) => loop(fn, targs ::: targss, argss) + case AppliedTypeTree(fn, targs) => + loop(fn, targs ::: targss, argss) case _ => (tree, targss, argss) } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 0d77795fc19c..8b1f96185515 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -995,7 +995,7 @@ object Types { /** If this type is a typeref with a type lambda as alias or upper bound, widen to the lambda */ final def toLambda(implicit ctx: Context): Type = widen match { - case tp: TypeRef if tp.info.hiBound.isInstanceOf[LambdaType] => tp.info.hiBound + case tp: TypeProxy if tp.superType.isInstanceOf[LambdaType] => tp.superType case tp => tp } diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 99e0a3e23c6c..a2cc3e94ebcf 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -255,7 +255,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => matchArgs(orderedArgs, methType.paramInfos, 0) case _ => if (methType.isError) ok = false - else fail(s"$methString does not take parameters?") + else fail(s"$methString does not take parameters") } /** The application was successful */ @@ -691,7 +691,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def realApply(implicit ctx: Context): Tree = track("realApply") { val originalProto = new FunProto(tree.args, IgnoredProto(pt), this)(argCtx(tree)) - val fun1 = typedExpr(tree.fun, originalProto) + val fun1 = typed(tree.fun, originalProto)(ctx.retractMode(Mode.Pattern)) // Warning: The following lines are dirty and fragile. We record that auto-tupling was demanded as // a side effect in adapt. If it was, we assume the tupled proto-type in the rest of the application, diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 5b790435cb47..5bf96ed629a1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -147,9 +147,13 @@ trait NamerContextOps { this: Context => } else HKTermLambda.fromParams(params, resultType) } - if (typeParams.nonEmpty) PolyType.fromParams(typeParams, monotpe) - else if (valueParamss.isEmpty) ExprType(monotpe) - else monotpe + if (sym.isTerm) + if (typeParams.nonEmpty) PolyType.fromParams(typeParams, monotpe) + else if (valueParamss.isEmpty) ExprType(monotpe) + else monotpe + else + if (typeParams.nonEmpty) HKTypeLambda.fromParams(typeParams, monotpe) + else monotpe } /** Add moduleClass or sourceModule functionality to completer diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 213e8f2b476c..c80b9ef88ada 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2158,14 +2158,12 @@ class Typer extends Namer false } - def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp match { - case _: MethodOrPoly => + def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp.toLambda match { + case _: LambdaType => if (pt.args.lengthCompare(1) > 0 && isUnary(wtp) && ctx.canAutoTuple) adapt(tree, pt.tupled, locked) else tree - case wtp: TypeRef if wtp.underlying.hiBound.isInstanceOf[HKLambda] => - tree case _ => tryInsertApplyOrImplicit(tree, pt, locked) { errorTree(tree, MethodDoesNotTakeParameters(tree)) } @@ -2489,8 +2487,9 @@ class Typer extends Namer } def adaptType(tp: Type): Tree = { + val isApplied = pt `eq` AnyTypeConstructorProto val tree1 = - if ((pt eq AnyTypeConstructorProto) || tp.typeParamSymbols.isEmpty) tree + if (isApplied || tp.typeParamSymbols.isEmpty) tree else { val tp1 = if (ctx.compilationUnit.isJava) @@ -2507,7 +2506,7 @@ class Typer extends Namer case pt: TypeBounds if ctx.mode `is` Mode.Type => pt `contains` tree1.tpe case _ => tree1.tpe <:< pt } - if (Inliner.isInlineable(tree.symbol)) + if (!isApplied && Inliner.isInlineable(tree.symbol)) readaptSimplified { Inliner.inlineCall(tree, pt) match { case inlined: Inlined => TypeTree(inlined.tpe).withPos(tree.pos) diff --git a/tests/run/typelevel2.scala b/tests/run/typelevel2.scala index db203227b545..f7768b7ca11f 100644 --- a/tests/run/typelevel2.scala +++ b/tests/run/typelevel2.scala @@ -34,4 +34,15 @@ object Test extends App { def ii: Int = 2 def nn: ToNat(ii) = S(S(Z)) // no expansion possible, since `ii` is of type `Int`. val ii1: Int = nn.toInt + + type nth[F[_], T](n: Int) = + if n == 0 then T + else F[nth[F, T](n - 1)] + + val nth0: nth[List, Int](0) = 2 + val nth1: nth[Option, String](1) = Some("hi") + val nth3: nth[Seq, Boolean](3) = Seq(Seq(Seq(true, false))) + val nth0a: Int = nth0 + val nth1a: Option[String] = nth1 + val nth3a: Seq[Seq[Seq[Boolean]]] = nth3 } \ No newline at end of file From 6be4781d033b79375d53f7c5a1ae5a31cc6a0b06 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 Jun 2018 18:04:23 +0200 Subject: [PATCH 54/64] Make Apply a Term- or Type-Tree Depending on what it's function part is. --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index c5c9028259e9..a152c988f09a 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -434,11 +434,13 @@ object Trees { def forwardTo = qual } - abstract class GenericApply[-T >: Untyped] extends ProxyTree[T] with TermTree[T] { + abstract class GenericApply[-T >: Untyped] extends ProxyTree[T] { type ThisTree[-T >: Untyped] <: GenericApply[T] val fun: Tree[T] val args: List[Tree[T]] def forwardTo = fun + override def isTerm = fun.isTerm + override def isType = fun.isType } /** fun(args) */ From ca304940a4deafd964333069546d87e610c6147e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 Jun 2018 19:13:16 +0200 Subject: [PATCH 55/64] Make hole reading context-independent Currently the translation of a hole depends on whether we are reading a type tree or a term tree, but that distinction is fickle. --- .../tools/dotc/core/tasty/TreeUnpickler.scala | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 4a1707a69002..cb86aaf3caad 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -350,7 +350,7 @@ class TreeUnpickler(reader: TastyReader, case binder: LambdaType => binder.paramRefs(readNat()) } case HOLE => - readHole(end, isType = true).tpe + readHole(end).tpe } assert(currentAddr == end, s"$start $currentAddr $end ${astTagToString(tag)}") result @@ -1148,7 +1148,7 @@ class TreeUnpickler(reader: TastyReader, val hi = if (currentAddr == end) lo else readTpt() TypeBoundsTree(lo, hi) case HOLE => - readHole(end, isType = false) + readHole(end) case UNTYPEDSPLICE => tpd.UntypedSplice(readUntyped()).withType(readType()) case _ => @@ -1204,7 +1204,7 @@ class TreeUnpickler(reader: TastyReader, owner => new LazyReader(localReader, owner, ctx.mode, op) } - def readHole(end: Addr, isType: Boolean)(implicit ctx: Context): Tree = { + def readHole(end: Addr)(implicit ctx: Context): Tree = { val idx = readNat() val args = until(end)(readTerm()) val splice = splices(idx) @@ -1212,12 +1212,9 @@ class TreeUnpickler(reader: TastyReader, if (arg.isTerm) new TastyTreeExpr(arg) else new TreeType(arg) val reifiedArgs = args.map(wrap) - if (isType) { - val quotedType = splice.asInstanceOf[Seq[Any] => quoted.Type[_]](reifiedArgs) - PickledQuotes.quotedTypeToTree(quotedType) - } else { - val quotedExpr = splice.asInstanceOf[Seq[Any] => quoted.Expr[_]](reifiedArgs) - PickledQuotes.quotedExprToTree(quotedExpr) + splice.asInstanceOf[Seq[Any] => Any](reifiedArgs) match { + case spliced: quoted.Type[_] => PickledQuotes.quotedTypeToTree(spliced) + case spliced: quoted.Expr[_] => PickledQuotes.quotedExprToTree(spliced) } } // ------ Reading untyped trees -------------------------------------------- From bc73cd1e18dfda6d8ead1130cfa6ee0d2cb778dc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 30 Jun 2018 12:10:23 +0200 Subject: [PATCH 56/64] Tasty: Have a tpt form for Apply as well as TypeApply Needed to accurately reflect parameterized type applications. We now use APPLYtpt/TYPEAPPLYtpt instead of APPLIEDtpt. --- .../tools/dotc/core/tasty/TastyFormat.scala | 19 +++++++++++-------- .../tools/dotc/core/tasty/TreePickler.scala | 8 ++++---- .../tools/dotc/core/tasty/TreeUnpickler.scala | 6 ++++-- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index dd56815f8727..877dcea21a1b 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -104,7 +104,8 @@ Standard-Section: "ASTs" TopLevelStat* SELECTtpt NameRef qual_Term SINGLETONtpt ref_Term REFINEDtpt Length underlying_Term refinement_Stat* - APPLIEDtpt Length tycon_Term arg_Term* + APPLYtpt Length tycon_Term arg_Term* + TYPEAPPLYtpt Length tycon_Term arg_Term* POLYtpt Length TypeParam* body_Term TYPEBOUNDStpt Length low_Term high_Term? ANNOTATEDtpt Length underlying_Term fullAnnotation_Term @@ -247,7 +248,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 */ @@ -409,7 +410,7 @@ object TastyFormat { final val REFINEDtype = 158 final val REFINEDtpt = 159 final val APPLIEDtype = 160 - final val APPLIEDtpt = 161 + final val TYPEAPPLYtpt = 161 final val TYPEBOUNDS = 162 final val TYPEBOUNDStpt = 163 final val ANDtype = 164 @@ -423,7 +424,7 @@ object TastyFormat { final val ANNOTATION = 172 final val TERMREFin = 173 final val TYPEREFin = 174 - final val OBJECTDEF = 175 + final val APPLYtpt = 175 // In binary: 101100EI // I = implicit method type @@ -432,6 +433,7 @@ object TastyFormat { final val IMPLICITMETHODtype = 177 final val ERASEDMETHODtype = 178 final val ERASEDIMPLICITMETHODtype = 179 + final val OBJECTDEF = 180 final val UNTYPEDSPLICE = 199 @@ -507,15 +509,15 @@ object TastyFormat { | SELECTtpt | SINGLETONtpt | REFINEDtpt - | APPLIEDtpt + | APPLYtpt + | TYPEAPPLYtpt | LAMBDAtpt | TYPEBOUNDStpt | ANNOTATEDtpt | ANDtpt | ORtpt | BYNAMEtpt - | BIND - | APPLY => true + | BIND => true case _ => false } @@ -632,7 +634,8 @@ object TastyFormat { case REFINEDtype => "REFINEDtype" case REFINEDtpt => "REFINEDtpt" case APPLIEDtype => "APPLIEDtype" - case APPLIEDtpt => "APPLIEDtpt" + case APPLYtpt => "APPLYtpt" + case TYPEAPPLYtpt => "TYPEAPPLYtpt" case TYPEBOUNDS => "TYPEBOUNDS" case TYPEBOUNDStpt => "TYPEBOUNDStpt" case TYPEALIAS => "TYPEALIAS" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 0b6fd4392732..0f0c7901c6a3 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -365,14 +365,14 @@ class TreePickler(pickler: TastyPickler) { writeByte(THROW) pickleTree(args.head) } else { - writeByte(APPLY) + writeByte(if (tree.isType) APPLYtpt else APPLY) withLength { pickleTree(fun) args.foreach(pickleTree) } } case TypeApply(fun, args) => - writeByte(TYPEAPPLY) + writeByte(if (tree.isType) TYPEAPPLYtpt else TYPEAPPLY) withLength { pickleTree(fun) args.foreach(pickleTpt) @@ -524,7 +524,7 @@ class TreePickler(pickler: TastyPickler) { withLength { pickleTree(parent); refinements.foreach(pickleTree) } } case AppliedTypeTree(tycon, args) => - writeByte(APPLIEDtpt) + writeByte(TYPEAPPLYtpt) withLength { pickleTree(tycon); args.foreach(pickleTree) } case AndTypeTree(tp1, tp2) => writeByte(ANDtpt) @@ -819,7 +819,7 @@ class TreePickler(pickler: TastyPickler) { writeByte(REFINEDtpt) withLength { pickleTpt(parent); refinements.foreach(pickleTerm) } case AppliedTypeTree(tycon, args) => - writeByte(APPLIEDtpt) + writeByte(TYPEAPPLYtpt) withLength { pickleTpt(tycon); args.foreach(pickleTpt) } case AndTypeTree(tp1, tp2) => writeByte(ANDtpt) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index cb86aaf3caad..4a899e93619f 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1123,7 +1123,9 @@ class TreeUnpickler(reader: TastyReader, val parent = readTpt() val refinements = readStats(refineCls, end)(localContext(refineCls)) RefinedTypeTree(parent, refinements, refineCls) - case APPLIEDtpt => + case APPLYtpt => + tpd.Apply(readTpt(), until(end)(readTerm())) + case TYPEAPPLYtpt => // If we do directly a tpd.AppliedType tree we might get a // wrong number of arguments in some scenarios reading F-bounded // types. This came up in #137 of collection strawman. @@ -1374,7 +1376,7 @@ class TreeUnpickler(reader: TastyReader, untpd.Import(readUntyped(), readSelectors()) case REFINEDtpt => untpd.RefinedTypeTree(readUntyped(), until(end)(readUntyped())) - case APPLIEDtpt => + case TYPEAPPLYtpt => untpd.AppliedTypeTree(readUntyped(), until(end)(readUntyped())) case ANDtpt => untpd.AndTypeTree(readUntyped(), readUntyped()) From 96c1d22df7f3cae88a46bac5fe832e4a936f6725 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 30 Jun 2018 13:25:27 +0200 Subject: [PATCH 57/64] Drop AppliedTypeTree With the introduction of `Apply` nodes as types, we were facing an imbalance: Type appications distinguished between type trees and term trees by having two classes, `AppliedTypeTree` and `TypeApply`. Term applications did not. In the interest of keeping the number of tree classes low, we now use `TypeApply for both type and term trees that are applied to type tree arguments. The only exception is in the Tasty format, where we need to distinguish the two, because otherwise a hole followed by arguments would be ambiguous. Distinguishing between terms and types in the Tasty serialization format is done systematically everywhere. It has the advantage that it increases redundancy. But for the internal tree representation it's better to have as few classes as possible. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 10 +- .../dotty/tools/dotc/ast/DesugarEnums.scala | 2 +- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 7 +- compiler/src/dotty/tools/dotc/ast/Trees.scala | 16 --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 3 - compiler/src/dotty/tools/dotc/ast/untpd.scala | 8 +- .../src/dotty/tools/dotc/core/StdNames.scala | 1 - .../tools/dotc/core/tasty/TreePickler.scala | 11 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 4 +- .../core/unpickleScala2/Scala2Unpickler.scala | 2 +- .../tools/dotc/parsing/JavaParsers.scala | 8 +- .../dotty/tools/dotc/parsing/Parsers.scala | 4 +- .../tools/dotc/parsing/ScriptParsers.scala | 2 +- .../tools/dotc/printing/RefinedPrinter.scala | 4 +- .../tools/dotc/tastyreflect/TastyImpl.scala | 2 +- .../tools/dotc/transform/FirstTransform.scala | 4 +- .../dotty/tools/dotc/transform/LinkAll.scala | 2 +- .../tools/dotc/transform/PostTyper.scala | 34 +++-- .../tools/dotc/transform/TreeChecker.scala | 1 - .../src/dotty/tools/dotc/typer/Checking.scala | 11 +- .../dotty/tools/dotc/typer/Inferencing.scala | 2 +- .../dotty/tools/dotc/typer/TypeAssigner.scala | 128 +++++++++--------- .../src/dotty/tools/dotc/typer/Typer.scala | 30 ++-- docs/docs/internals/syntax.md | 2 +- 24 files changed, 129 insertions(+), 169 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index f01bd304a644..e8c292399bf4 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -357,7 +357,7 @@ object desugar { val classTycon: Tree = new TypeRefTree // watching is set at end of method def appliedTypeTree(tycon: Tree, args: List[Tree]) = - (if (args.isEmpty) tycon else AppliedTypeTree(tycon, args)) + (if (args.isEmpty) tycon else TypeApply(tycon, args)) .withPos(cdef.pos.startPos) def isHK(tparam: Tree): Boolean = tparam match { @@ -371,7 +371,7 @@ object desugar { val targ = refOfDef(tparam) def fullyApplied(tparam: Tree): Tree = tparam match { case TypeDef(_, LambdaTypeTree(tparams, body)) => - AppliedTypeTree(targ, tparams.map(_ => TypeBoundsTree(EmptyTree, EmptyTree))) + TypeApply(targ, tparams.map(_ => TypeBoundsTree(EmptyTree, EmptyTree))) case TypeDef(_, rhs: DerivedTypeTree) => fullyApplied(rhs.watched) case _ => @@ -1113,7 +1113,7 @@ object desugar { Apply(Select(Apply(Ident(nme.StringContext), strs), id), elems) case InfixOp(l, op, r) => if (ctx.mode is Mode.Type) - AppliedTypeTree(op, l :: r :: Nil) // op[l, r] + TypeApply(op, l :: r :: Nil) // op[l, r] else { assert(ctx.mode is Mode.Pattern) // expressions are handled separately by `binop` Apply(op, l :: r :: Nil) // op(l, r) @@ -1122,7 +1122,7 @@ object desugar { if ((ctx.mode is Mode.Type) && !op.isBackquoted && op.name == tpnme.raw.STAR) { val seqType = if (ctx.compilationUnit.isJava) defn.ArrayType else defn.SeqType Annotated( - AppliedTypeTree(ref(seqType), t), + TypeApply(ref(seqType), t :: Nil), New(ref(defn.RepeatedAnnotType), Nil :: Nil)) } else { assert(ctx.mode.isExpr || ctx.reporter.hasErrors || ctx.mode.is(Mode.Interactive), ctx.mode) @@ -1138,7 +1138,7 @@ object desugar { ctx.error(TupleTooLong(ts), tree.pos) unitLiteral } else if (arity == 1) ts.head - else if (ctx.mode is Mode.Type) AppliedTypeTree(ref(tupleTypeRef), ts) + else if (ctx.mode is Mode.Type) TypeApply(ref(tupleTypeRef), ts) else if (arity == 0) unitLiteral else Apply(ref(tupleTypeRef.classSymbol.companionModule.termRef), ts) case WhileDo(cond, body) => diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index ebb3157a5733..c140c9eb5588 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -143,7 +143,7 @@ object DesugarEnums { def extractType(t: Tree): Tree = t match { case Apply(t1, _) => extractType(t1) - case TypeApply(t1, ts) => AppliedTypeTree(extractType(t1), ts) + case TypeApply(t1, ts) => TypeApply(extractType(t1), ts) case Select(t1, nme.CONSTRUCTOR) => extractType(t1) case New(t1) => t1 case t1 => t1 diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 6a7a3d1fe689..d50f53703ddd 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -72,7 +72,6 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] => */ def methPart(tree: Tree): Tree = stripApply(tree) match { case TypeApply(fn, _) => methPart(fn) - case AppliedTypeTree(fn, _) => methPart(fn) // !!! should not be needed case Block(stats, expr) => methPart(expr) case mp => mp } @@ -152,7 +151,7 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] => def isRepeatedParamType(tpt: Tree)(implicit ctx: Context): Boolean = tpt match { case ByNameTypeTree(tpt1) => isRepeatedParamType(tpt1) case tpt: TypeTree => tpt.typeOpt.isRepeatedParam - case AppliedTypeTree(Select(_, tpnme.REPEATED_PARAM_CLASS), _) => true + case TypeApply(Select(_, tpnme.REPEATED_PARAM_CLASS), _) => true case _ => false } @@ -164,7 +163,7 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] => case AndTypeTree(tpt1, tpt2) => mayBeTypePat(tpt1) || mayBeTypePat(tpt2) case OrTypeTree(tpt1, tpt2) => mayBeTypePat(tpt1) || mayBeTypePat(tpt2) case RefinedTypeTree(tpt, refinements) => mayBeTypePat(tpt) || refinements.exists(_.isInstanceOf[Bind]) - case AppliedTypeTree(tpt, args) => mayBeTypePat(tpt) || args.exists(_.isInstanceOf[Bind]) + case TypeApply(tpt, args) => mayBeTypePat(tpt) || args.exists(_.isInstanceOf[Bind]) case Select(tpt, _) => mayBeTypePat(tpt) case Annotated(tpt, _) => mayBeTypePat(tpt) case _ => false @@ -578,8 +577,6 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => loop(fn, targss, args :: argss) case TypeApply(fn, targs) => loop(fn, targs ::: targss, argss) - case AppliedTypeTree(fn, targs) => - loop(fn, targs ::: targss, argss) case _ => (tree, targss, argss) } diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index a152c988f09a..63b02cf66a8a 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -634,13 +634,6 @@ object Trees { def forwardTo = tpt } - /** tpt[args] */ - case class AppliedTypeTree[-T >: Untyped] private[ast] (tpt: Tree[T], args: List[Tree[T]]) - extends ProxyTree[T] with TypTree[T] { - type ThisTree[-T >: Untyped] = AppliedTypeTree[T] - def forwardTo = tpt - } - /** [typeparams] -> tpt */ case class LambdaTypeTree[-T >: Untyped] private[ast] (tparams: List[TypeDef[T]], body: Tree[T]) extends TypTree[T] { @@ -898,7 +891,6 @@ object Trees { type AndTypeTree = Trees.AndTypeTree[T] type OrTypeTree = Trees.OrTypeTree[T] type RefinedTypeTree = Trees.RefinedTypeTree[T] - type AppliedTypeTree = Trees.AppliedTypeTree[T] type LambdaTypeTree = Trees.LambdaTypeTree[T] type ByNameTypeTree = Trees.ByNameTypeTree[T] type TypeBoundsTree = Trees.TypeBoundsTree[T] @@ -1060,10 +1052,6 @@ object Trees { case tree: RefinedTypeTree if (tpt eq tree.tpt) && (refinements eq tree.refinements) => tree case _ => finalize(tree, untpd.RefinedTypeTree(tpt, refinements)) } - def AppliedTypeTree(tree: Tree)(tpt: Tree, args: List[Tree]): AppliedTypeTree = tree match { - case tree: AppliedTypeTree if (tpt eq tree.tpt) && (args eq tree.args) => tree - case _ => finalize(tree, untpd.AppliedTypeTree(tpt, args)) - } def LambdaTypeTree(tree: Tree)(tparams: List[TypeDef], body: Tree): LambdaTypeTree = tree match { case tree: LambdaTypeTree if (tparams eq tree.tparams) && (body eq tree.body) => tree case _ => finalize(tree, untpd.LambdaTypeTree(tparams, body)) @@ -1217,8 +1205,6 @@ object Trees { cpy.OrTypeTree(tree)(transform(left), transform(right)) case RefinedTypeTree(tpt, refinements) => cpy.RefinedTypeTree(tree)(transform(tpt), transformSub(refinements)) - case AppliedTypeTree(tpt, args) => - cpy.AppliedTypeTree(tree)(transform(tpt), transform(args)) case LambdaTypeTree(tparams, body) => implicit val ctx = localCtx cpy.LambdaTypeTree(tree)(transformSub(tparams), transform(body)) @@ -1349,8 +1335,6 @@ object Trees { this(this(x, left), right) case RefinedTypeTree(tpt, refinements) => this(this(x, tpt), refinements) - case AppliedTypeTree(tpt, args) => - this(this(x, tpt), args) case LambdaTypeTree(tparams, body) => implicit val ctx = localCtx this(this(x, tparams), body) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index efdd3a00edd3..371d24015734 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -148,9 +148,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def RefinedTypeTree(parent: Tree, refinements: List[Tree], refineCls: ClassSymbol)(implicit ctx: Context): Tree = ta.assignType(untpd.RefinedTypeTree(parent, refinements), parent, refinements, refineCls) - def AppliedTypeTree(tycon: Tree, args: List[Tree])(implicit ctx: Context): AppliedTypeTree = - ta.assignType(untpd.AppliedTypeTree(tycon, args), tycon, args) - def ByNameTypeTree(result: Tree)(implicit ctx: Context): ByNameTypeTree = ta.assignType(untpd.ByNameTypeTree(result), result) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index de5a371b75c6..8c26f4aaacfe 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -286,7 +286,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def AndTypeTree(left: Tree, right: Tree): AndTypeTree = new AndTypeTree(left, right) def OrTypeTree(left: Tree, right: Tree): OrTypeTree = new OrTypeTree(left, right) def RefinedTypeTree(tpt: Tree, refinements: List[Tree]): RefinedTypeTree = new RefinedTypeTree(tpt, refinements) - def AppliedTypeTree(tpt: Tree, args: List[Tree]): AppliedTypeTree = new AppliedTypeTree(tpt, args) def LambdaTypeTree(tparams: List[TypeDef], body: Tree): LambdaTypeTree = new LambdaTypeTree(tparams, body) def ByNameTypeTree(result: Tree): ByNameTypeTree = new ByNameTypeTree(result) def TypeBoundsTree(lo: Tree, hi: Tree): TypeBoundsTree = new TypeBoundsTree(lo, hi) @@ -309,9 +308,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { */ def New(tpt: Tree, argss: List[List[Tree]])(implicit ctx: Context): Tree = { val (tycon, targs) = tpt match { - case AppliedTypeTree(tycon, targs) => + case TypeApply(tycon, targs) => (tycon, targs) - case TypedSplice(AppliedTypeTree(tycon, targs)) => + case TypedSplice(TypeApply(tycon, targs)) => (TypedSplice(tycon), targs map (TypedSplice(_))) case TypedSplice(tpt1: tpd.Tree) => val tycon = tpt1.tpe.typeConstructor @@ -337,9 +336,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case _ => Apply(tpt, Nil) } - def AppliedTypeTree(tpt: Tree, arg: Tree): AppliedTypeTree = - AppliedTypeTree(tpt, arg :: Nil) - def TypeTree(tpe: Type)(implicit ctx: Context): TypedSplice = TypedSplice(TypeTree().withTypeUnchecked(tpe)) def unitLiteral = Literal(Constant(())) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index ff7a3abed486..9d96e6f557b1 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -324,7 +324,6 @@ object StdNames { // Compiler utilized names val AnnotatedType: N = "AnnotatedType" - val AppliedTypeTree: N = "AppliedTypeTree" val ArrayAnnotArg: N = "ArrayAnnotArg" val Constant: N = "Constant" val ConstantType: N = "ConstantType" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 0f0c7901c6a3..17651cc4f3e3 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -373,10 +373,7 @@ class TreePickler(pickler: TastyPickler) { } case TypeApply(fun, args) => writeByte(if (tree.isType) TYPEAPPLYtpt else TYPEAPPLY) - withLength { - pickleTree(fun) - args.foreach(pickleTpt) - } + withLength { pickleTree(fun); args.foreach(pickleTpt) } case Literal(const1) => pickleConstant { tree.tpe match { @@ -523,9 +520,6 @@ class TreePickler(pickler: TastyPickler) { refinements.foreach(preRegister) withLength { pickleTree(parent); refinements.foreach(pickleTree) } } - case AppliedTypeTree(tycon, args) => - writeByte(TYPEAPPLYtpt) - withLength { pickleTree(tycon); args.foreach(pickleTree) } case AndTypeTree(tp1, tp2) => writeByte(ANDtpt) withLength { pickleTree(tp1); pickleTree(tp2) } @@ -818,9 +812,6 @@ class TreePickler(pickler: TastyPickler) { case RefinedTypeTree(parent, refinements) => writeByte(REFINEDtpt) withLength { pickleTpt(parent); refinements.foreach(pickleTerm) } - case AppliedTypeTree(tycon, args) => - writeByte(TYPEAPPLYtpt) - withLength { pickleTpt(tycon); args.foreach(pickleTpt) } case AndTypeTree(tp1, tp2) => writeByte(ANDtpt) withLength { pickleTpt(tp1); pickleTpt(tp2) } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 4a899e93619f..1e514c6b22f7 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1131,7 +1131,7 @@ class TreeUnpickler(reader: TastyReader, // types. This came up in #137 of collection strawman. val tycon = readTpt() val args = until(end)(readTpt()) - untpd.AppliedTypeTree(tycon, args).withType(tycon.tpe.safeAppliedTo(args.tpes)) + untpd.TypeApply(tycon, args).withType(tycon.tpe.safeAppliedTo(args.tpes)) case ANDtpt => val tpt1 = readTpt() val tpt2 = readTpt() @@ -1376,8 +1376,6 @@ class TreeUnpickler(reader: TastyReader, untpd.Import(readUntyped(), readSelectors()) case REFINEDtpt => untpd.RefinedTypeTree(readUntyped(), until(end)(readUntyped())) - case TYPEAPPLYtpt => - untpd.AppliedTypeTree(readUntyped(), until(end)(readUntyped())) case ANDtpt => untpd.AndTypeTree(readUntyped(), readUntyped()) case ORtpt => diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index f7dbda218e0e..3bd852e93586 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -1210,7 +1210,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas case APPLIEDTYPEtree => val tpt = readTreeRef() val args = until(end, () => readTreeRef()) - AppliedTypeTree(tpt, args) + TypeApply(tpt, args) case TYPEBOUNDStree => val lo = readTreeRef() diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index 024026c984eb..0007c32da6da 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -105,7 +105,7 @@ object JavaParsers { def javaLangObject(): Tree = javaLangDot(tpnme.Object) def arrayOf(tpt: Tree) = - AppliedTypeTree(Ident(nme.Array.toTypeName), List(tpt)) + TypeApply(Ident(nme.Array.toTypeName), List(tpt)) def unimplementedExpr(implicit ctx: Context) = Select(Select(rootDot(nme.scala_), nme.Predef), nme.???) @@ -230,7 +230,7 @@ object JavaParsers { def convertToTypeId(tree: Tree): Tree = convertToTypeName(tree) match { case Some(t) => t withPos tree.pos case _ => tree match { - case AppliedTypeTree(_, _) | Select(_, _) => + case TypeApply(_, _) | Select(_, _) => tree case _ => syntaxError(IdentifierExpected(tree.show), tree.pos) @@ -327,7 +327,7 @@ object JavaParsers { val args = repsep(() => typeArg(), COMMA) acceptClosingAngle() atPos(t1.pos.start) { - AppliedTypeTree(t1, args) + TypeApply(t1, args) } } else t } @@ -835,7 +835,7 @@ object JavaParsers { accept(RBRACE) /* val superclazz = - AppliedTypeTree(javaLangDot(tpnme.Enum), List(enumType)) + TypeApply(javaLangDot(tpnme.Enum), List(enumType)) */ val superclazz = Apply(TypeApply( Select(New(javaLangDot(tpnme.Enum)), nme.CONSTRUCTOR), List(enumType)), diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 36ea9087eba8..af332774ce2d 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -901,7 +901,7 @@ object Parsers { private def simpleTypeRest(t: Tree): Tree = in.token match { case HASH => simpleTypeRest(typeProjection(t)) case LBRACKET => simpleTypeRest(atPos(startOffset(t)) { - AppliedTypeTree(checkWildcard(t), typeArgs(namedOK = false, wildOK = true)) }) + TypeApply(checkWildcard(t), typeArgs(namedOK = false, wildOK = true)) }) case _ => t } @@ -996,7 +996,7 @@ object Parsers { def contextBounds(pname: TypeName): List[Tree] = in.token match { case COLON => atPos(in.skipToken()) { - AppliedTypeTree(toplevelTyp(), Ident(pname)) + TypeApply(toplevelTyp(), Ident(pname) :: Nil) } :: contextBounds(pname) case VIEWBOUND => deprecationWarning("view bounds `<%' are deprecated, use a context bound `:' instead") diff --git a/compiler/src/dotty/tools/dotc/parsing/ScriptParsers.scala b/compiler/src/dotty/tools/dotc/parsing/ScriptParsers.scala index afa7fefab860..a8923fe2bdb3 100644 --- a/compiler/src/dotty/tools/dotc/parsing/ScriptParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/ScriptParsers.scala @@ -127,7 +127,7 @@ object ScriptParsers { ) // def main - def mainParamType = AppliedTypeTree(Ident(tpnme.Array), List(Ident(tpnme.String))) + def mainParamType = TypeApply(Ident(tpnme.Array), List(Ident(tpnme.String))) def mainParameter = List(ValDef(Modifiers(Param), "argv", mainParamType, EmptyTree)) def mainSetArgv = List(ValDef(Modifiers(), "args", TypeTree(), Ident("argv"))) def mainNew = makeNew(Nil, emptyValDef, stmts, List(Nil), NoPosition, NoPosition) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 0d952786e967..a11322abbaf9 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -261,7 +261,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { changePrec(GlobalPrec) { keywordStr("for ") ~ Text(enums map enumText, "; ") ~ sep ~ toText(expr) } def cxBoundToText(bound: untpd.Tree): Text = bound match { // DD - case AppliedTypeTree(tpt, _) => " : " ~ toText(tpt) + case TypeApply(tpt, _) => " : " ~ toText(tpt) case untpd.Function(_, tpt) => " <% " ~ toText(tpt) } @@ -379,8 +379,6 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { changePrec(OrPrec) { toText(l) ~ " | " ~ toText(r) } case RefinedTypeTree(tpt, refines) => toTextLocal(tpt) ~ " " ~ blockText(refines) - case AppliedTypeTree(tpt, args) => - toTextLocal(tpt) ~ "[" ~ Text(args map argText, ", ") ~ "]" case LambdaTypeTree(tparams, body) => changePrec(GlobalPrec) { tparamsText(tparams) ~ " -> " ~ toText(body) diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/TastyImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/TastyImpl.scala index fdd78ccc4ed7..09317f501fbf 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/TastyImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/TastyImpl.scala @@ -669,7 +669,7 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s object Applied extends AppliedExtractor { def unapply(x: TypeTree)(implicit ctx: Context): Option[(TypeTree, List[TypeOrBoundsTree])] = x match { - case x: tpd.AppliedTypeTree @unchecked => Some(x.tpt, x.args) + case x: tpd.TypeApply @unchecked => Some(x.fun, x.args) case _ => None } } diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 92230e38310b..9902c7241162 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -153,10 +153,10 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => if (tree.isType) toTypeTree(tree) else constToLiteral(tree) override def transformTypeApply(tree: TypeApply)(implicit ctx: Context) = - constToLiteral(tree) + if (tree.isType) toTypeTree(tree) else constToLiteral(tree) override def transformApply(tree: Apply)(implicit ctx: Context) = - constToLiteral(foldCondition(tree)) + if (tree.isType) toTypeTree(tree) else constToLiteral(foldCondition(tree)) override def transformTyped(tree: Typed)(implicit ctx: Context) = constToLiteral(tree) diff --git a/compiler/src/dotty/tools/dotc/transform/LinkAll.scala b/compiler/src/dotty/tools/dotc/transform/LinkAll.scala index 5fa9436f297a..153bc0815a92 100644 --- a/compiler/src/dotty/tools/dotc/transform/LinkAll.scala +++ b/compiler/src/dotty/tools/dotc/transform/LinkAll.scala @@ -44,7 +44,7 @@ class LinkAll extends Phase { private[this] var inParents = false override def apply(acc: Set[ClassDenotation], tree: tpd.Tree)(implicit ctx: Context): Set[ClassDenotation] = tree match { case New(tpt) => accum(acc, tpt.tpe.classSymbol) - case AppliedTypeTree(tpt, _) if inParents => accum(acc, tpt.symbol) + case TypeApply(tpt, _) if tpt.isType && inParents => accum(acc, tpt.symbol) case tree: RefTree if inParents || tree.symbol.is(Module) => foldOver(accum(acc, tree.symbol), tree) case tree @ Template(constr, parents, self, _) => diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 918d85afeca6..742b99b2b450 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -30,7 +30,7 @@ object PostTyper { * * (5) Convert all trees representing types to TypeTrees. * - * (6) Check the bounds of AppliedTypeTrees + * (6) Check the bounds of TypeApply type trees * * (7) Insert `.package` for selections of package object members * @@ -203,18 +203,25 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase super.transform(tree) } case tree: TypeApply => - val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree) - if (fn.symbol != defn.ChildAnnot.primaryConstructor) { - // Make an exception for ChildAnnot, which should really have AnyKind bounds - Checking.checkBounds(args, fn.tpe.widen.asInstanceOf[PolyType]) + // TODO simplify the following + if (tree.isType) { + Checking.checkAppliedType(tree, boundsCheck = !ctx.mode.is(Mode.Pattern)) + super.transform(tree) } - fn match { - case sel: Select => - val args1 = transform(args) - val sel1 = transformSelect(sel, args1) - cpy.TypeApply(tree1)(sel1, args1) - case _ => - super.transform(tree1) + else { + val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree) + if (fn.symbol != defn.ChildAnnot.primaryConstructor) { + // Make an exception for ChildAnnot, which should really have AnyKind bounds + Checking.checkBounds(args, fn.tpe.widen.asInstanceOf[PolyType]) + } + fn match { + case sel: Select => + val args1 = transform(args) + val sel1 = transformSelect(sel, args1) + cpy.TypeApply(tree1)(sel1, args1) + case _ => + super.transform(tree1) + } } case Inlined(call, bindings, expansion) => // Leave only a call trace consisting of @@ -264,9 +271,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase super.transform(tree) case tree @ Annotated(annotated, annot) => cpy.Annotated(tree)(transform(annotated), transformAnnot(annot)) - case tree: AppliedTypeTree => - Checking.checkAppliedType(tree, boundsCheck = !ctx.mode.is(Mode.Pattern)) - super.transform(tree) case SingletonTypeTree(ref) => Checking.checkRealizable(ref.tpe, ref.pos.focus) super.transform(tree) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 85fbc123f785..a0afefaa9d6a 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -249,7 +249,6 @@ class TreeChecker extends Phase with SymTransformer { // case tree: AndTypeTree => // case tree: OrTypeTree => // case tree: RefinedTypeTree => - // case tree: AppliedTypeTree => // case tree: ByNameTypeTree => // case tree: TypeBoundsTree => // case tree: Alternative => diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index e909906a8b7a..aa6ffaff1686 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -33,9 +33,8 @@ import dotty.tools.dotc.transform.ValueClasses._ object Checking { import tpd._ - /** A general checkBounds method that can be used for TypeApply nodes as - * well as for AppliedTypeTree nodes. Also checks that type arguments to - * *-type parameters are fully applied. + /** A general checkBounds method that can be used for TypeApply nodes in term and type form. + * Also checks that type arguments to *-type parameters are fully applied. */ def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type)(implicit ctx: Context): Unit = { (args, boundss).zipped.foreach { (arg, bound) => @@ -51,8 +50,6 @@ object Checking { } /** Check that type arguments `args` conform to corresponding bounds in `tl` - * Note: This does not check the bounds of AppliedTypeTrees. These - * are handled by method checkBounds in FirstTransform */ def checkBounds(args: List[tpd.Tree], tl: TypeLambda)(implicit ctx: Context): Unit = checkBounds(args, tl.paramInfos, _.substParams(tl, _)) @@ -64,8 +61,8 @@ object Checking { * Unreducible applications correspond to general existentials, and we * cannot handle those. */ - def checkAppliedType(tree: AppliedTypeTree, boundsCheck: Boolean)(implicit ctx: Context) = { - val AppliedTypeTree(tycon, args) = tree + def checkAppliedType(tree: TypeApply, boundsCheck: Boolean)(implicit ctx: Context) = { + val TypeApply(tycon, args) = tree // If `args` is a list of named arguments, return corresponding type parameters, // otherwise return type parameters unchanged val tparams = tycon.tpe.typeParams diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index b0218c03d766..a288fe2578aa 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -146,7 +146,7 @@ object Inferencing { def inferTypeParams(tree: Tree, pt: Type)(implicit ctx: Context): Tree = tree.tpe match { case tl: TypeLambda => val (tl1, tvars) = constrained(tl, tree) - var tree1 = AppliedTypeTree(tree.withType(tl1), tvars) + var tree1 = TypeApply(tree.withType(tl1), tvars) tree1.tpe <:< pt fullyDefinedType(tree1.tpe, "template parent", tree.pos) tree1 diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index ce468f56bf98..5347c9e524e9 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -376,63 +376,72 @@ trait TypeAssigner { } def assignType(tree: untpd.TypeApply, fn: Tree, args: List[Tree])(implicit ctx: Context) = { - val ownType = fn.tpe.widen match { - case pt: TypeLambda => - val paramNames = pt.paramNames - if (hasNamedArg(args)) { - val paramBoundsByName = paramNames.zip(pt.paramInfos).toMap - - // Type arguments which are specified by name (immutable after this first loop) - val namedArgMap = new mutable.HashMap[Name, Type] - for (NamedArg(name, arg) <- args) - if (namedArgMap.contains(name)) - ctx.error(DuplicateNamedTypeParameter(name), arg.pos) - else if (!paramNames.contains(name)) - ctx.error(UndefinedNamedTypeParameter(name, paramNames), arg.pos) - else - namedArgMap(name) = arg.tpe - - // Holds indexes of non-named typed arguments in paramNames - val gapBuf = new mutable.ListBuffer[Int] - def nextPoly(idx: Int) = { - val newIndex = gapBuf.length - gapBuf += idx - // Re-index unassigned type arguments that remain after transformation - pt.paramRefs(newIndex) - } - - // Type parameters after naming assignment, conserving paramNames order - val normArgs: List[Type] = paramNames.zipWithIndex.map { case (pname, idx) => - namedArgMap.getOrElse(pname, nextPoly(idx)) - } - - val transform = new TypeMap { - def apply(t: Type) = t match { - case TypeParamRef(`pt`, idx) => normArgs(idx) - case _ => mapOver(t) + val ownType = + if (tree.isTerm) + fn.tpe.widen match { + case pt: TypeLambda => + val paramNames = pt.paramNames + if (hasNamedArg(args)) { + assert(fn.isTerm) + val paramBoundsByName = paramNames.zip(pt.paramInfos).toMap + + // Type arguments which are specified by name (immutable after this first loop) + val namedArgMap = new mutable.HashMap[Name, Type] + for (NamedArg(name, arg) <- args) + if (namedArgMap.contains(name)) + ctx.error(DuplicateNamedTypeParameter(name), arg.pos) + else if (!paramNames.contains(name)) + ctx.error(UndefinedNamedTypeParameter(name, paramNames), arg.pos) + else + namedArgMap(name) = arg.tpe + + // Holds indexes of non-named typed arguments in paramNames + val gapBuf = new mutable.ListBuffer[Int] + def nextPoly(idx: Int) = { + val newIndex = gapBuf.length + gapBuf += idx + // Re-index unassigned type arguments that remain after transformation + pt.paramRefs(newIndex) + } + + // Type parameters after naming assignment, conserving paramNames order + val normArgs: List[Type] = paramNames.zipWithIndex.map { case (pname, idx) => + namedArgMap.getOrElse(pname, nextPoly(idx)) + } + + val transform = new TypeMap { + def apply(t: Type) = t match { + case TypeParamRef(`pt`, idx) => normArgs(idx) + case _ => mapOver(t) + } + } + val resultType1 = transform(pt.resultType) + if (gapBuf.isEmpty) resultType1 + else { + val gaps = gapBuf.toList + pt.derivedLambdaType( + gaps.map(paramNames), + gaps.map(idx => transform(pt.paramInfos(idx)).bounds), + resultType1) + } } - } - val resultType1 = transform(pt.resultType) - if (gapBuf.isEmpty) resultType1 - else { - val gaps = gapBuf.toList - pt.derivedLambdaType( - gaps.map(paramNames), - gaps.map(idx => transform(pt.paramInfos(idx)).bounds), - resultType1) - } - } - else { - val argTypes = args.tpes - if (sameLength(argTypes, paramNames)) pt.instantiate(argTypes) - else wrongNumberOfTypeArgs(fn.tpe, pt.typeParams, args, tree.pos) + else { + val argTypes = args.tpes + if (sameLength(argTypes, paramNames)) pt.instantiate(argTypes) + else wrongNumberOfTypeArgs(fn.tpe, pt.typeParams, args, tree.pos) + } + case err: ErrorType => + err + case _ => + //println(i"bad type: $fn: ${fn.symbol} / ${fn.symbol.isType} / ${fn.symbol.info}") // DEBUG + errorType(err.takesNoParamsStr(fn, "type "), tree.pos) } - case err: ErrorType => - err - case _ => - //println(i"bad type: $fn: ${fn.symbol} / ${fn.symbol.isType} / ${fn.symbol.info}") // DEBUG - errorType(err.takesNoParamsStr(fn, "type "), tree.pos) - } + else { + assert(!hasNamedArg(args)) + val tparams = fn.tpe.typeParams + if (sameLength(tparams, args)) fn.tpe.appliedTo(args.tpes) + else wrongNumberOfTypeArgs(fn.tpe, tparams, args, tree.pos) + } tree.withType(ownType) } @@ -505,15 +514,6 @@ trait TypeAssigner { tree.withType(RecType.closeOver(rt => refined.substThis(refineCls, rt.recThis))) } - def assignType(tree: untpd.AppliedTypeTree, tycon: Tree, args: List[Tree])(implicit ctx: Context) = { - assert(!hasNamedArg(args)) - val tparams = tycon.tpe.typeParams - val ownType = - if (sameLength(tparams, args)) tycon.tpe.appliedTo(args.tpes) - else wrongNumberOfTypeArgs(tycon.tpe, tparams, args, tree.pos) - tree.withType(ownType) - } - def assignType(tree: untpd.LambdaTypeTree, tparamDefs: List[TypeDef], body: Tree)(implicit ctx: Context) = tree.withType(HKTypeLambda.fromParams(tparamDefs.map(_.symbol.asType), body.tpe)) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c80b9ef88ada..3c8b515b15da 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -519,7 +519,7 @@ class Typer extends Namer checkClassType(tpt1.tpe, tpt1.pos, traitReq = false, stablePrefixReq = true) tpt1 match { - case AppliedTypeTree(_, targs) => + case TypeApply(_, targs) => for (targ @ TypeBoundsTree(_, _) <- targs) ctx.error(WildcardOnTypeArgumentNotAllowedOnNew(), targ.pos) case _ => @@ -783,7 +783,7 @@ class Typer extends Namer val resTpt = TypeTree(mt.nonDependentResultApprox).withPos(body.pos) val typeArgs = params1.map(_.tpt) :+ resTpt val tycon = TypeTree(funCls.typeRef) - val core = assignType(cpy.AppliedTypeTree(tree)(tycon, typeArgs), tycon, typeArgs) + val core = assignType(cpy.TypeApply(tree)(tycon, typeArgs), tycon, typeArgs) val appMeth = ctx.newSymbol(ctx.owner, nme.apply, Synthetic | Deferred, mt) val appDef = assignType( untpd.DefDef(appMeth.name, Nil, List(params1), resultTpt, EmptyTree), @@ -796,7 +796,7 @@ class Typer extends Namer typedDependent(args.asInstanceOf[List[ValDef]])( ctx.fresh.setOwner(ctx.newRefinedClassSymbol(tree.pos)).setNewScope) case _ => - typed(cpy.AppliedTypeTree(tree)(untpd.TypeTree(funCls.typeRef), args :+ body), pt) + typed(cpy.TypeApply(tree)(untpd.TypeTree(funCls.typeRef), args :+ body), pt) } } @@ -1206,18 +1206,18 @@ class Typer extends Namer assignType(cpy.RefinedTypeTree(tree)(tpt1, refinements1), tpt1, refinements1, refineCls) } - def typedAppliedTypeTree(tree: untpd.AppliedTypeTree)(implicit ctx: Context): Tree = track("typedAppliedTypeTree") { - val tpt1 = typed(tree.tpt, AnyTypeConstructorProto)(ctx.retractMode(Mode.Pattern)) - val tparams = tpt1.tpe.typeParams + def typedAppliedTypeTree(tree: untpd.TypeApply)(implicit ctx: Context): Tree = track("typedAppliedTypeTree") { + val tycon = typed(tree.fun, AnyTypeConstructorProto)(ctx.retractMode(Mode.Pattern)) + val tparams = tycon.tpe.typeParams if (tparams.isEmpty) { - ctx.error(TypeDoesNotTakeParameters(tpt1.tpe, tree.args), tree.pos) - tpt1 + ctx.error(TypeDoesNotTakeParameters(tycon.tpe, tree.args), tree.pos) + tycon } else { var args = tree.args val args1 = { if (args.length != tparams.length) { - wrongNumberOfTypeArgs(tpt1.tpe, tparams, args, tree.pos) + wrongNumberOfTypeArgs(tycon.tpe, tparams, args, tree.pos) args = args.take(tparams.length) } def typedArg(arg: untpd.Tree, tparam: ParamInfo) = { @@ -1226,7 +1226,7 @@ class Typer extends Namer (if (untpd.isVarPattern(arg)) desugar.patternVar(arg) else arg, tparam.paramInfo) else (arg, WildcardType) - if (tpt1.symbol.isClass) + if (tycon.symbol.isClass) tparam match { case tparam: Symbol => tparam.ensureCompleted() // This is needed to get the test `compileParSetSubset` to work @@ -1237,7 +1237,7 @@ class Typer extends Namer arg match { case TypeBoundsTree(EmptyTree, EmptyTree) if tparam.paramInfo.isLambdaSub && - tpt1.tpe.typeParamSymbols.nonEmpty && + tycon.tpe.typeParamSymbols.nonEmpty && !ctx.mode.is(Mode.Pattern) => // An unbounded `_` automatically adapts to type parameter bounds. This means: // If we have wildcard application C[_], where `C` is a class replace @@ -1245,7 +1245,7 @@ class Typer extends Namer // type parameter in `C`, avoiding any referemces to parameters of `C`. // The transform does not apply for patters, where empty bounds translate to // wildcard identifiers `_` instead. - res = res.withType(avoid(tparam.paramInfo, tpt1.tpe.typeParamSymbols)) + res = res.withType(avoid(tparam.paramInfo, tycon.tpe.typeParamSymbols)) case _ => } res @@ -1264,7 +1264,7 @@ class Typer extends Namer } val args2 = preCheckKinds(args1, paramBounds) // check that arguments conform to bounds is done in phase PostTyper - assignType(cpy.AppliedTypeTree(tree)(tpt1, args2), tpt1, args2) + assignType(cpy.TypeApply(tree)(tycon, args2), tycon, args2) } } @@ -1805,7 +1805,8 @@ class Typer extends Namer case tree: untpd.Return => typedReturn(tree) case tree: untpd.Try => typedTry(tree, pt) case tree: untpd.Throw => typedThrow(tree) - case tree: untpd.TypeApply => typedTypeApply(tree, pt) + case tree: untpd.TypeApply => + if (tree.isTerm) typedTypeApply(tree, pt) else typedAppliedTypeTree(tree) case tree: untpd.Super => typedSuper(tree, pt) case tree: untpd.SeqLiteral => typedSeqLiteral(tree, pt) case tree: untpd.Inlined => typedInlined(tree, pt) @@ -1814,7 +1815,6 @@ class Typer extends Namer case tree: untpd.AndTypeTree => typedAndTypeTree(tree) case tree: untpd.OrTypeTree => typedOrTypeTree(tree) case tree: untpd.RefinedTypeTree => typedRefinedTypeTree(tree) - case tree: untpd.AppliedTypeTree => typedAppliedTypeTree(tree) case tree: untpd.LambdaTypeTree => typedLambdaTypeTree(tree)(ctx.localContext(tree, NoSymbol).setNewScope) case tree: untpd.ByNameTypeTree => typedByNameTypeTree(tree) case tree: untpd.TypeBoundsTree => typedTypeBoundsTree(tree, pt) diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 41bd23403617..f6aa74fc5761 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -130,7 +130,7 @@ RefinedType ::= WithType {[nl] Refinement} WithType ::= AnnotType {‘with’ AnnotType} (deprecated) AnnotType ::= AppliedType {Annotation} Annotated(t, annot) AppliedType ::= SimpleType {ParArgumentExprs} Apply(t, args) -SimpleType ::= SimpleType TypeArgs AppliedTypeTree(t, args) +SimpleType ::= SimpleType TypeArgs TypeApply(t, args) | SimpleType ‘#’ id Select(t, name) | StableId | [‘-’ | ‘+’ | ‘~’ | ‘!’] StableId PrefixOp(expr, op) From 9a545e5afb489cbf541e7561450c1159e870b436 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 30 Jun 2018 18:42:27 +0200 Subject: [PATCH 58/64] Adapt Namer.checkFlags to type defdefs Converted flags to termflags before. --- .../src/dotty/tools/dotc/typer/Namer.scala | 10 +++++-- tests/neg/typemethods.scala | 20 +++++++++++++ tests/neg/typemethods2.scala | 28 +++++++++++++++++++ 3 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 tests/neg/typemethods.scala create mode 100644 tests/neg/typemethods2.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 5bf96ed629a1..a6ecf1ff34d4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -284,10 +284,14 @@ class Namer { typer: Typer => def checkFlags(flags: FlagSet) = if (flags.isEmpty) flags else { - val (ok, adapted, kind) = tree match { - case tree: TypeDef => (flags.isTypeFlags, flags.toTypeFlags, "type") - case _ => (flags.isTermFlags, flags.toTermFlags, "value") + val isType = tree match { + case tree: TypeDef => true + case tree: DefDef => tree.name.isTypeName + case _ => false } + val (ok, adapted, kind) = + if (isType) (flags.isTypeFlags, flags.toTypeFlags, "type") + else (flags.isTermFlags, flags.toTermFlags, "value") if (!ok) ctx.error(i"modifier(s) `$flags' incompatible with $kind definition", tree.pos) adapted diff --git a/tests/neg/typemethods.scala b/tests/neg/typemethods.scala new file mode 100644 index 000000000000..efcf7630c8e3 --- /dev/null +++ b/tests/neg/typemethods.scala @@ -0,0 +1,20 @@ +trait Nat { + def toInt: Int +} + +case object Z extends Nat { + transparent def toInt = 0 +} + +case class S[N <: Nat](n: N) extends Nat { + transparent def toInt = n.toInt + 1 +} + +object Test extends App { + + type ToNat(n: Int) <: Nat = + if n == 0 then Z.type + else S[ToNat(n - 1)] + + type ToNat = Int // error: double definition +} \ No newline at end of file diff --git a/tests/neg/typemethods2.scala b/tests/neg/typemethods2.scala new file mode 100644 index 000000000000..aa8aad6e0890 --- /dev/null +++ b/tests/neg/typemethods2.scala @@ -0,0 +1,28 @@ +trait Nat { + def toInt: Int +} + +case object Z extends Nat { + transparent def toInt = 0 +} + +case class S[N <: Nat](n: N) extends Nat { + transparent def toInt = n.toInt + 1 +} + +class C { + type ToNat = Int + + type ToNat2(n: Int) <: Nat = + if n == 0 then Z.type + else S[ToNat2(n - 1)]} + +object Test extends C { + + override type ToNat(n: Int) <: Nat = // error: illegal override + if n == 0 then Z.type + else S[ToNat(n - 1)] + + override type ToNat2[X] = X // error: illegal override + +} \ No newline at end of file From 85945b049a4cbbddad5f53b786b06c6f3e7da474 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 1 Jul 2018 10:42:25 +0200 Subject: [PATCH 59/64] Make `toLambda` not force too much `toLambda` would always compute the denotation of a TypeRef, which makes it problematic as a widening operator, since it might force too much. It now widens only if the corresponding symbol is a TypeMethod. --- compiler/src/dotty/tools/dotc/core/Types.scala | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 8b1f96185515..ab806b852203 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -993,10 +993,19 @@ object Types { case _ => this } - /** If this type is a typeref with a type lambda as alias or upper bound, widen to the lambda */ - final def toLambda(implicit ctx: Context): Type = widen match { - case tp: TypeProxy if tp.superType.isInstanceOf[LambdaType] => tp.superType - case tp => tp + /** If this type is a typeref or applied type referring to a TypeMethid with a type + * lambda as alias or upper bound, widen to the lambda. + */ + final def toLambda(implicit ctx: Context): Type = { + def isLambda(tp: Type): Boolean = tp match { + case tp: TypeRef => tp.symbol.is(TypeMethod) + case tp: AppliedType => isLambda(tp.tycon) + case _ => false + } + this match { + case tp: TypeProxy if isLambda(tp) && tp.superType.isInstanceOf[LambdaType] => tp.superType + case _ => this + } } /** If this type contains embedded union types, replace them by their joins. From 703edead4ab4e7e2397bbc42301fa0e01a722535 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 30 Jun 2018 19:50:19 +0200 Subject: [PATCH 60/64] Handle HKTermLambdas in TypeComparer --- .../dotty/tools/dotc/core/TypeComparer.scala | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 4efcb0648c12..b0fdad877d23 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -446,8 +446,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { // of an `AndType` can lead to a cascade of subtyping checks // This twist is needed to make collection/generic/ParFactory.scala compile fourthTry || compareRefinedSlow - case tp1: HKTypeLambda => - // HKTypeLambdas do not have members. + case tp1: HKLambda => + // HKLambdas do not have members. fourthTry case _ => compareRefinedSlow || fourthTry @@ -534,16 +534,16 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { either(recur(tp1, tp21), recur(tp1, tp22)) || fourthTry case tp2: MethodType => def compareMethod = tp1 match { - case tp1: MethodType => compareMethodOrPoly(tp1, tp2) + case tp1: MethodType => compareLambdaTypes(tp1, tp2) case _ => false } compareMethod - case tp2: PolyType => - def comparePoly = tp1 match { - case tp1: PolyType => compareMethodOrPoly(tp1, tp2) + case tp2: LambdaType => // either HKTermLambda or PolyType + def compareLambda = tp1 match { + case tp1: LambdaType if tp1.companion `eq` tp2.companion => compareLambdaTypes(tp1, tp2) case _ => false } - comparePoly + compareLambda case tp2 @ ExprType(restpe2) => def compareExpr = tp1 match { // We allow ()T to be a subtype of => T. @@ -582,7 +582,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { fourthTry } - def compareMethodOrPoly(tp1: MethodOrPoly, tp2: MethodOrPoly) = + def compareLambdaTypes(tp1: LambdaType, tp2: LambdaType) = (tp1.signature consistentParams tp2.signature) && matchingParams(tp1, tp2) && (!tp2.isImplicitMethod || tp1.isImplicitMethod) && @@ -1177,7 +1177,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { /** Do lambda types `lam1` and `lam2` have parameters that have the same types * and the same implicit status? (after renaming one set to the other) */ - def matchingParams(lam1: MethodOrPoly, lam2: MethodOrPoly): Boolean = { + def matchingParams(lam1: LambdaType, lam2: LambdaType): Boolean = { /** Are `syms1` and `syms2` parameter lists with pairwise equivalent types? */ def loop(formals1: List[Type], formals2: List[Type]): Boolean = formals1 match { case formal1 :: rest1 => From e96512818b04e26b9d917472af82a0cfeda442a1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 30 Jun 2018 19:51:07 +0200 Subject: [PATCH 61/64] Handle HKTermLambdas in Printer --- compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index f922b4bebbe6..aabd7da2c9a5 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -172,7 +172,8 @@ class PlainPrinter(_ctx: Context) extends Printer { ("(" + (if (tp.isErasedMethod) "erased " else "") + (if (tp.isImplicitMethod) "implicit " else "") ) ~ paramsText(tp) ~ - (if (tp.resultType.isInstanceOf[MethodType]) ")" else "): ") ~ + (if (tp.isInstanceOf[TermLambda] || + tp.resultType.isInstanceOf[MethodType]) ")" else "): ") ~ toText(tp.resultType) } case tp: ExprType => @@ -326,7 +327,7 @@ class PlainPrinter(_ctx: Context) extends Printer { else dclsText(trueDecls) tparamsText ~ " extends " ~ toTextParents(tp.parents) ~ "{" ~ selfText ~ declsText ~ "} at " ~ preText - case mt: MethodType => + case mt: LambdaType => toTextGlobal(mt) case tp: ExprType => ": => " ~ toTextGlobal(tp.widenExpr) From ff14afa73793c452c1d922d2905220260df89afb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 30 Jun 2018 19:51:23 +0200 Subject: [PATCH 62/64] Handle HKTermLambdas in Tasty and TastyImpl --- .../dotty/tools/dotc/tastyreflect/TastyImpl.scala | 15 +++++++++++++++ library/src/scala/tasty/Tasty.scala | 14 ++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/TastyImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/TastyImpl.scala index 09317f501fbf..6dd63b263e91 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/TastyImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/TastyImpl.scala @@ -759,12 +759,14 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s type LambdaType[ParamInfo <: TypeOrBounds] = Types.LambdaType { type PInfo = ParamInfo } type MethodType = Types.MethodType type PolyType = Types.PolyType + type TermLambda = Types.TermLambda type TypeLambda = Types.TypeLambda def typeClassTag: ClassTag[Type] = implicitly[ClassTag[Type]] def recursiveTypeClassTag: ClassTag[RecursiveType] = implicitly[ClassTag[RecursiveType]] def methodTypeClassTag: ClassTag[MethodType] = implicitly[ClassTag[MethodType]] def polyTypeClassTag: ClassTag[PolyType] = implicitly[ClassTag[PolyType]] + def termLambdaClassTag: ClassTag[TermLambda] = implicitly[ClassTag[TermLambda]] def typeLambdaClassTag: ClassTag[TypeLambda] = implicitly[ClassTag[TypeLambda]] def MethodTypeDeco(tpe: MethodType): MethodTypeAPI = new MethodTypeAPI { @@ -781,6 +783,12 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s def resultTpe(implicit ctx: Context): Type = tpe.resType } + def TermLambdaDeco(tpe: Types.TermLambda): TermLambdaAPI = new TermLambdaAPI { + def paramNames(implicit ctx: Context): List[String] = tpe.paramNames.map(_.toString) + def paramTypes(implicit ctx: Context): List[Type] = tpe.paramInfos + def resultTpe(implicit ctx: Context): Type = tpe.resType + } + def TypeLambdaDeco(tpe: Types.TypeLambda): TypeLambdaAPI = new TypeLambdaAPI { def paramNames(implicit ctx: Context): List[String] = tpe.paramNames.map(_.toString) def paramTypes(implicit ctx: Context): List[TypeBounds] = tpe.paramInfos @@ -928,6 +936,13 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s } } + object TermLambda extends TermLambdaExtractor { + def unapply(x: TermLambda)(implicit ctx: Context): Option[(List[String], List[Type], Type)] = x match { + case x: TermLambda => Some(x.paramNames.map(_.toString), x.paramInfos, x.resType) + case _ => None + } + } + object TypeLambda extends TypeLambdaExtractor { def unapply(x: TypeLambda)(implicit ctx: Context): Option[(List[String], List[TypeBounds], Type)] = x match { case x: TypeLambda => Some(x.paramNames.map(_.toString), x.paramInfos, x.resType) diff --git a/library/src/scala/tasty/Tasty.scala b/library/src/scala/tasty/Tasty.scala index 7b8d830f0d2c..704c8fd1b641 100644 --- a/library/src/scala/tasty/Tasty.scala +++ b/library/src/scala/tasty/Tasty.scala @@ -593,11 +593,13 @@ abstract class Tasty { tasty => type LambdaType[ParamInfo <: TypeOrBounds] <: Type type MethodType <: LambdaType[Type] type PolyType <: LambdaType[TypeBounds] + type TermLambda <: LambdaType[Type] type TypeLambda <: LambdaType[TypeBounds] implicit def typeClassTag: ClassTag[Type] implicit def methodTypeClassTag: ClassTag[MethodType] implicit def polyTypeClassTag: ClassTag[PolyType] + implicit def termLambdaClassTag: ClassTag[TermLambda] implicit def typeLambdaClassTag: ClassTag[TypeLambda] implicit def recursiveTypeClassTag: ClassTag[RecursiveType] @@ -617,6 +619,13 @@ abstract class Tasty { tasty => } implicit def PolyTypeDeco(tpt: PolyType): PolyTypeAPI + trait TermLambdaAPI { + def paramNames(implicit ctx: Context): List[String] + def paramTypes(implicit ctx: Context): List[Type] + def resultTpe(implicit ctx: Context): Type + } + implicit def TermLambdaDeco(tpt: TermLambda): TermLambdaAPI + trait TypeLambdaAPI { def paramNames(implicit ctx: Context): List[String] def paramTypes(implicit ctx: Context): List[TypeBounds] @@ -714,6 +723,11 @@ abstract class Tasty { tasty => def unapply(x: PolyType)(implicit ctx: Context): Option[(List[String], List[TypeBounds], Type)] } + val TermLambda: TermLambdaExtractor + abstract class TermLambdaExtractor { + def unapply(x: TermLambda)(implicit ctx: Context): Option[(List[String], List[Type], Type)] + } + val TypeLambda: TypeLambdaExtractor abstract class TypeLambdaExtractor { def unapply(x: TypeLambda)(implicit ctx: Context): Option[(List[String], List[TypeBounds], Type)] From cf82715902ce0484062602572ed7a249d4b9095f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 30 Jun 2018 19:57:17 +0200 Subject: [PATCH 63/64] Disallow curried type methods Curried type methods open the possibility of partial applications. But this would give us full dependent typing, with types containing arbitrary terms (namely the arguments of the partial application). --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 3 ++- docs/docs/internals/syntax.md | 2 +- tests/neg/typemethods.scala | 5 +++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index af332774ce2d..18f53797ac35 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2200,7 +2200,7 @@ object Parsers { Block(stats, Literal(Constant(()))) } - /** TypeDcl ::= id [TypTypeParamClause] {DefParamClause} TypeBounds ‘=’ TypeRHS + /** TypeDcl ::= id [TypTypeParamClause] [DefParamClause] TypeBounds ‘=’ TypeRHS * | id [HkTypeParamClause] TypeBounds */ def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = { @@ -2209,6 +2209,7 @@ object Parsers { val name = ident().toTypeName val tparams = typeParamClauseOpt(ParamOwner.Type) val vparamss = paramClauses(ParamOwner.Type) + if (vparamss.length > 1) syntaxError(i"type definitions cannot have multiple parameter clauses") val isBounded = in.token == SUPERTYPE || in.token == SUBTYPE val bounds = typeBounds() val res = diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index f6aa74fc5761..0d1fc54779b1 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -313,7 +313,7 @@ ValDcl ::= ids ‘:’ Type VarDcl ::= ids ‘:’ Type PatDef(_, ids, tpe, EmptyTree) DefDcl ::= DefSig [‘:’ Type] DefDef(_, name, tparams, vparamss, tpe, EmptyTree) DefSig ::= id [DefTypeParamClause] DefParamClauses -TypeDcl ::= id [TypTypeParamClause] {DefParamClause} TypeBounds DefDef(name, tparams, vparamss, bounds, rhs) +TypeDcl ::= id [TypTypeParamClause] [DefParamClause] TypeBounds DefDef(name, tparams, vparamss, bounds, rhs) ‘=’ TypeRHS | id [HkTypeParamClause] TypeBounds TypeDef(name, tparams, bounds) diff --git a/tests/neg/typemethods.scala b/tests/neg/typemethods.scala index efcf7630c8e3..face996e196c 100644 --- a/tests/neg/typemethods.scala +++ b/tests/neg/typemethods.scala @@ -17,4 +17,9 @@ object Test extends App { else S[ToNat(n - 1)] type ToNat = Int // error: double definition + + type Foo[T](n: Int)(m: Int) = Int // error: cannot be curried + + type Bar(implicit m: Int) = Int // error: cannot be implicit + type Baz(erased m: Int) = Int // error: cannot be erased } \ No newline at end of file From 6e43b8a25381dcca50b1be1fd5a457d094d839f9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 1 Jul 2018 15:23:53 +0200 Subject: [PATCH 64/64] Fix printing of method types --- compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index aabd7da2c9a5..74da6bb04367 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -172,7 +172,7 @@ class PlainPrinter(_ctx: Context) extends Printer { ("(" + (if (tp.isErasedMethod) "erased " else "") + (if (tp.isImplicitMethod) "implicit " else "") ) ~ paramsText(tp) ~ - (if (tp.isInstanceOf[TermLambda] || + (if (tp.isInstanceOf[HKTermLambda] || tp.resultType.isInstanceOf[MethodType]) ")" else "): ") ~ toText(tp.resultType) }