diff options
Diffstat (limited to 'compiler/src')
6 files changed, 156 insertions, 17 deletions
diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 6c27e53f6..c80107f93 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1719,7 +1719,7 @@ object Types { * under both private and public names, so it could still be found by looking up * the public name. */ - final def shadowed(implicit ctx: Context): NamedType = + def shadowed(implicit ctx: Context): NamedType = NamedType(prefix, name.shadowedName) override def equals(that: Any) = that match { @@ -1782,15 +1782,19 @@ object Types { else d.atSignature(sig).checkUnique } - override def newLikeThis(prefix: Type)(implicit ctx: Context): TermRef = { - val candidate = TermRef.withSig(prefix, name, sig) + private def fixDenot(candidate: TermRef, prefix: Type)(implicit ctx: Context): TermRef = if (symbol.exists && !candidate.symbol.exists) { // recompute from previous symbol val ownSym = symbol val newd = asMemberOf(prefix, allowPrivate = ownSym.is(Private)) candidate.withDenot(newd.suchThat(_.signature == ownSym.signature)) } else candidate - } + + override def newLikeThis(prefix: Type)(implicit ctx: Context): TermRef = + fixDenot(TermRef.withSig(prefix, name, sig), prefix) + + override def shadowed(implicit ctx: Context): NamedType = + fixDenot(TermRef.withSig(prefix, name.shadowedName, sig), prefix) override def equals(that: Any) = that match { case that: TermRefWithSignature => diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 65c7a290d..b644c94cc 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -12,7 +12,7 @@ import core._ import Flags._ import Contexts._ import Names._ -import ast.Positioned +import ast.{Positioned, Trees, untpd} import ast.Trees._ import Decorators._ import StdNames._ @@ -20,6 +20,7 @@ import util.Positions._ import Constants._ import ScriptParsers._ import Comments._ + import scala.annotation.{tailrec, switch} import util.DotClass import rewrite.Rewrites.patch @@ -1800,6 +1801,10 @@ object Parsers { case _ => syntaxError(AuxConstructorNeedsNonImplicitParameter(), start) } } + val listOfErrors = checkVarArgsRules(result) + listOfErrors.foreach { vparam => + syntaxError(VarArgsParamMustComeLast(), vparam.tpt.pos) + } result } @@ -1921,6 +1926,21 @@ object Parsers { } } + + + private def checkVarArgsRules(vparamss: List[List[untpd.ValDef]]): List[untpd.ValDef] = { + def isVarArgs(tpt: Trees.Tree[Untyped]): Boolean = tpt match { + case PostfixOp(_, op) if op.name == nme.raw.STAR => true + case _ => false + } + + vparamss.flatMap { params => + if (params.nonEmpty) { + params.init.filter(valDef => isVarArgs(valDef.tpt)) + } else List() + } + } + /** DefDef ::= DefSig (`:' Type [`=' Expr] | "=" Expr) * | this ParamClause ParamClauses `=' ConstrExpr * DefDcl ::= DefSig `:' Type diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 6fa056646..57365658e 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -1209,4 +1209,16 @@ object messages { |${parents.mkString(" - ", "\n - ", "")} |""".stripMargin } + + case class VarArgsParamMustComeLast()(implicit ctx: Context) + extends Message(IncorrectRepeatedParameterSyntaxID) { + override def msg: String = "varargs parameter must come last" + + override def kind: String = "Syntax" + + override def explanation: String = + hl"""|The varargs field must be the last field in the method signature. + |Attempting to define a field in a method signature after a varargs field is an error. + |""".stripMargin + } } diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 43202ef87..082a80b58 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -114,6 +114,11 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder private[this] val classLikeCache = new mutable.HashMap[ClassSymbol, api.ClassLike] /** This cache is optional, it avoids recomputing representations */ private[this] val typeCache = new mutable.HashMap[Type, api.Type] + /** This cache is necessary to avoid unstable name hashing when `typeCache` is present, + * see the comment in the `RefinedType` case in `computeType` + * The cache key is (api of RefinedType#parent, api of RefinedType#refinedInfo). + */ + private[this] val refinedTypeCache = new mutable.HashMap[(api.Type, api.Definition), api.Structure] private[this] object Constants { val emptyStringArray = Array[String]() @@ -349,7 +354,14 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder Constants.emptyType case tp: NamedType => val sym = tp.symbol - // Normalize package prefix to avoid instability of representation + // A type can sometimes be represented by multiple different NamedTypes + // (they will be `=:=` to each other, but not `==`), and the compiler + // may choose to use any of these representation, there is no stability + // guarantee. We avoid this instability by always normalizing the + // prefix: if it's a package, if we didn't do this sbt might conclude + // that some API changed when it didn't, leading to overcompilation + // (recompiling more things than what is needed for incremental + // compilation to be correct). val prefix = if (sym.owner.is(Package)) sym.owner.thisType else @@ -389,15 +401,39 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder new api.TypeDeclaration(apiType(lo), apiType(hi), Array(), name, Constants.public, Constants.emptyModifiers, Array()) } - - val decl: Array[api.Definition] = rt.refinedInfo match { + val decl = rt.refinedInfo match { case rinfo: TypeBounds => - Array(typeRefinement(name, rinfo)) + typeRefinement(name, rinfo) case _ => ctx.debuglog(i"sbt-api: skipped structural refinement in $rt") - Array() + null } - new api.Structure(strict2lzy(Array(parent)), strict2lzy(decl), strict2lzy(Array())) + + // Aggressive caching for RefinedTypes: `typeCache` is enough as long as two + // RefinedType are `==`, but this is only the case when their `refinedInfo` + // are `==` and this is not always the case, consider: + // + // val foo: { type Bla = a.b.T } + // val bar: { type Bla = a.b.T } + // + // The sbt API representations of `foo` and `bar` (let's call them `apiFoo` + // and `apiBar`) will both be instances of `Structure`. If `typeCache` was + // the only cache, then in some cases we would have `apiFoo eq apiBar` and + // in other cases we would just have `apiFoo == apiBar` (this happens + // because the dotty representation of `a.b.T` is unstable, see the comment + // in the `NamedType` case above). + // + // The fact that we may or may not have `apiFoo eq apiBar` is more than + // an optimisation issue: it will determine whether the sbt name hash for + // `Bla` contains one or two entries (because sbt `NameHashing` will not + // traverse both `apiFoo` and `apiBar` if they are `eq`), therefore the + // name hash of `Bla` will be unstable, unless we make sure that + // `apiFoo == apiBar` always imply `apiFoo eq apiBar`. This is what + // `refinedTypeCache` is for. + refinedTypeCache.getOrElseUpdate((parent, decl), { + val adecl: Array[api.Definition] = if (decl == null) Array() else Array(decl) + new api.Structure(strict2lzy(Array(parent)), strict2lzy(adecl), strict2lzy(Array())) + }) case tp: RecType => apiType(tp.parent) case RecThis(recType) => diff --git a/compiler/src/dotty/tools/dotc/sbt/ThunkHolder.scala b/compiler/src/dotty/tools/dotc/sbt/ThunkHolder.scala index e377de6da..abdd5cfdd 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ThunkHolder.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ThunkHolder.scala @@ -1,4 +1,5 @@ -package dotty.tools.dotc +package dotty.tools +package dotc package sbt import scala.annotation.tailrec @@ -24,7 +25,7 @@ private[sbt] trait ThunkHolder { * It will be forced by the next call to `forceThunks()` */ def lzy[T <: AnyRef](t: => T): api.Lazy[T] = { - val l = SafeLazy(() => t) + val l = SafeLazyWrapper(() => t) thunks += l l } @@ -37,10 +38,69 @@ private[sbt] trait ThunkHolder { * see https://github.com/sbt/zinc/issues/114 */ def strict2lzy[T <: AnyRef](t: T): api.Lazy[T] = - SafeLazy.strict(t) + SafeLazyWrapper.strict(t) } -// TODO: Use xsbti.SafeLazy once https://github.com/sbt/zinc/issues/113 is fixed +/** Wrapper around SafeLazy implementations. + * + * `xsbti.SafeLazy` is part of sbt but it is not part of the `interface` jar + * that dotty depends on, therefore we can only access it by reflection, + * and this will only succeed when dotty is run by sbt (otherwise + * `xsbti.SafeLazy` won't be on the classpath at all). + * + * For testing purposes, we still want to be able to run the sbt phases outside + * of sbt, using `-Yforce-sbt-phases` and `-Ydump-sbt-inc`, therefore we + * provide a copy of SafeLazy in `dotty.tools.dotc.sbt.SafeLazy` that we use + * when `xsbti.SafeLazy` is unavailable. + * + * This raises a question: why bother with `xsbti.SafeLazy` if we have our own + * version anyway? Because sbt uses Java serialization to persist the output of + * the incremental compilation analysis when sbt is stopped and restarted. If + * we used `dotty.tools.dotc.sbt.SafeLazy` with sbt, deserialization would fail + * and every restart of sbt would require a full recompilation. + * + * Note: this won't be needed once we switch to zinc 1.0 where `SafeLazy` becomes + * part of the `interface` jar, see https://github.com/sbt/zinc/issues/113 + */ +private object SafeLazyWrapper { + + @sharable private[this] val safeLazy = + try { + Class.forName("xsbti.SafeLazy") + } catch { + case e: ClassNotFoundException => + null + } + + @sharable private[this] val safeLazyApply = + if (safeLazy != null) + safeLazy.getMethod("apply", classOf[xsbti.F0[_]]) + else + null + @sharable private[this] val safeLazyStrict = + if (safeLazy != null) + safeLazy.getMethod("strict", classOf[Object]) + else + null + + def apply[T <: AnyRef](eval: () => T): xsbti.api.Lazy[T] = + if (safeLazyApply != null) + safeLazyApply + .invoke(null, new xsbti.F0[T] { def apply() = eval() }) + .asInstanceOf[xsbti.api.Lazy[T]] + else + SafeLazy(eval) + + def strict[T <: AnyRef](value: T): xsbti.api.Lazy[T] = + if (safeLazyStrict != null) + safeLazyStrict + .invoke(null, value) + .asInstanceOf[xsbti.api.Lazy[T]] + else + SafeLazy.strict(value) +} + +// Adapted from https://github.com/sbt/sbt/blob/0.13/compile/api/src/main/scala/xsbti/SafeLazy.scala private object SafeLazy { def apply[T <: AnyRef](eval: () => T): xsbti.api.Lazy[T] = new Impl(eval) diff --git a/compiler/src/dotty/tools/dotc/transform/ParamForwarding.scala b/compiler/src/dotty/tools/dotc/transform/ParamForwarding.scala index 859ac8b06..2836ebcfe 100644 --- a/compiler/src/dotty/tools/dotc/transform/ParamForwarding.scala +++ b/compiler/src/dotty/tools/dotc/transform/ParamForwarding.scala @@ -4,6 +4,8 @@ package transform import core._ import ast.Trees._ import Contexts._, Types._, Symbols._, Flags._, TypeUtils._, DenotTransformers._, StdNames._ +import Decorators._ +import config.Printers.typr /** For all parameter accessors * @@ -48,7 +50,7 @@ class ParamForwarding(thisTransformer: DenotTransformer) { val candidate = sym.owner.asClass.superClass .info.decl(sym.name).suchThat(_ is (ParamAccessor, butNot = Mutable)).symbol if (candidate.isAccessibleFrom(currentClass.thisType, superAccess = true)) candidate - else if (candidate is Method) inheritedAccessor(candidate) + else if (candidate.exists) inheritedAccessor(candidate) else NoSymbol } def forwardParamAccessor(stat: Tree): Tree = { @@ -65,8 +67,13 @@ class ParamForwarding(thisTransformer: DenotTransformer) { def forwarder(implicit ctx: Context) = { sym.copySymDenotation(initFlags = sym.flags | Method | Stable, info = sym.info.ensureMethodic) .installAfter(thisTransformer) - val superAcc = + var superAcc = Super(This(currentClass), tpnme.EMPTY, inConstrCall = false).select(alias) + if (alias.owner != currentClass.superClass) + // need to use shadowed in order not to accidentally address an + // intervening private forwarder in the superclass + superAcc = superAcc.withType(superAcc.tpe.asInstanceOf[TermRef].shadowed) + typr.println(i"adding param forwarder $superAcc") DefDef(sym, superAcc.ensureConforms(sym.info.widen)) } return forwarder(ctx.withPhase(thisTransformer.next)) |