diff --git a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala index 6c8b7b1d7358..bd8e3e3cd170 100644 --- a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala +++ b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala @@ -379,7 +379,8 @@ class CommunityBuildTest: @Test def scodecBits = projects.scodecBits.run() @Test def scodec = projects.scodec.run() @Test def scalaParserCombinators = projects.scalaParserCombinators.run() - @Test def dottyCpsAsync = projects.dottyCpsAsync.run() + // blocked on #9074 + //@Test def dottyCpsAsync = projects.dottyCpsAsync.run() @Test def scalaz = projects.scalaz.run() @Test def endpoints = projects.endpoints.run() end CommunityBuildTest diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 9a0ffb017975..e98cbb87cf1b 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -19,6 +19,7 @@ import printing.Printer import io.AbstractFile import config.Feature.migrateTo3 import config.Config +import config.Printers.overload import util.common._ import typer.ProtoTypes.NoViewsAllowed import collection.mutable.ListBuffer @@ -365,31 +366,32 @@ object Denotations { * * NoDenotations are dropped. MultiDenotations are handled by merging * parts with same signatures. SingleDenotations with equal signatures - * are joined as follows: + * are joined by following this sequence of steps: * - * In a first step, consider only those denotations which have symbols - * that are accessible from prefix `pre`. + * 1. If exactly one the denotations has an inaccessible symbol, pick the other one. + * 2. Otherwise, if one of the infos overrides the other one, and the associated + * symbol does not score strictly lower than the other one, + * pick the associated denotation. + * 3. Otherwise, if the two infos can be combined with `infoMeet`, pick that as + * result info, and pick the symbol that scores higher as result symbol, + * or pick `sym1` as a tie breaker. The picked info and symbol are combined + * in a JointDenotation. + * 4. Otherwise, if one of the two symbols scores strongly higher than the + * other one, pick the associated denotation. + * 5. Otherwise return a multi-denotation consisting of both denotations. * - * If there are several such denotations, try to pick one by applying the following - * three precedence rules in decreasing order of priority: + * Symbol scoring is determined according to the following ranking + * where earlier criteria trump later ones. Cases marked with (*) + * give a strong score advantage, the others a weak one. * - * 1. Prefer denotations with more specific infos. - * 2. If infos are equally specific, prefer denotations with concrete symbols over denotations - * with abstract symbols. - * 3. If infos are equally specific and symbols are equally concrete, - * prefer denotations with symbols defined in subclasses - * over denotations with symbols defined in proper superclasses. - * - * If there is exactly one (preferred) accessible denotation, return it. - * - * If there is no preferred accessible denotation, return a JointRefDenotation - * with one of the operand symbols (unspecified which one), and an info which - * is the intersection (using `&` or `safe_&` if `safeIntersection` is true) - * of the infos of the operand denotations. - * - * If SingleDenotations with different signatures are joined, return NoDenotation. + * 1. The symbol exists, and the other one does not. (*) + * 2. The symbol is not a bridge, but the other one is. (*) + * 3. The symbol is concrete, and the other one is deferred + * 4. The symbol appears before the other in the linearization of `pre` + * 5. The symbol's visibility is strictly greater than the other one's. + * 6. The symbol is a method, but the other one is not. */ - def & (that: Denotation, pre: Type, safeIntersection: Boolean = false)(implicit ctx: Context): Denotation = { + def meet(that: Denotation, pre: Type, safeIntersection: Boolean = false)(implicit ctx: Context): Denotation = { /** Try to merge denot1 and denot2 without adding a new signature. */ def mergeDenot(denot1: Denotation, denot2: SingleDenotation): Denotation = denot1 match { case denot1 @ MultiDenotation(denot11, denot12) => @@ -407,24 +409,27 @@ object Denotations { } /** Try to merge single-denotations. */ - def mergeSingleDenot(denot1: SingleDenotation, denot2: SingleDenotation): SingleDenotation = { + def mergeSingleDenot(denot1: SingleDenotation, denot2: SingleDenotation): Denotation = val info1 = denot1.info val info2 = denot2.info val sym1 = denot1.symbol val sym2 = denot2.symbol - val sym2Accessible = sym2.isAccessibleFrom(pre) + /** Does `owner1` come before `owner2` in the linearization of `pre`? */ + def linearScore(owner1: Symbol, owner2: Symbol): Int = - /** Does `sym1` come before `sym2` in the linearization of `pre`? */ - def precedes(sym1: Symbol, sym2: Symbol) = { - def precedesIn(bcs: List[ClassSymbol]): Boolean = bcs match { - case bc :: bcs1 => (sym1 eq bc) || !(sym2 eq bc) && precedesIn(bcs1) - case Nil => false - } - (sym1 ne sym2) && - (sym1.derivesFrom(sym2) || - !sym2.derivesFrom(sym1) && precedesIn(pre.baseClasses)) - } + def searchBaseClasses(bcs: List[ClassSymbol]): Int = bcs match + case bc :: bcs1 => + if bc eq owner1 then 1 + else if bc eq owner2 then -1 + else searchBaseClasses(bcs1) + case Nil => 0 + + if owner1 eq owner2 then 0 + else if owner1.derivesFrom(owner2) then 1 + else if owner2.derivesFrom(owner1) then -1 + else searchBaseClasses(pre.baseClasses) + end linearScore /** Similar to SymDenotation#accessBoundary, but without the special cases. */ def accessBoundary(sym: Symbol) = @@ -433,72 +438,52 @@ object Denotations { if (sym.is(Protected)) sym.owner.enclosingPackageClass else defn.RootClass) - /** Establish a partial order "preference" order between symbols. - * Give preference to `sym1` over `sym2` if one of the following - * conditions holds, in decreasing order of weight: - * 1. sym2 doesn't exist - * 2. sym1 is concrete and sym2 is abstract - * 3. The owner of sym1 comes before the owner of sym2 in the linearization - * of the type of the prefix `pre`. - * 4. The access boundary of sym2 is properly contained in the access - * boundary of sym1. For protected access, we count the enclosing - * package as access boundary. - * 5. sym1 is a method but sym2 is not. - * The aim of these criteria is to give some disambiguation on access which - * - does not depend on textual order or other arbitrary choices - * - minimizes raising of doubleDef errors - */ - def preferSym(sym1: Symbol, sym2: Symbol) = - sym1.eq(sym2) || - sym1.exists && - (!sym2.exists || - sym1.isAsConcrete(sym2) && - (!sym2.isAsConcrete(sym1) || - precedes(sym1.owner, sym2.owner) || - accessBoundary(sym2).isProperlyContainedIn(accessBoundary(sym1)) || - sym2.is(Bridge) && !sym1.is(Bridge) || - sym1.is(Method) && !sym2.is(Method)) || - sym1.info.isErroneous) - - /** Sym preference provided types also override */ - def prefer(sym1: Symbol, sym2: Symbol, info1: Type, info2: Type) = - preferSym(sym1, sym2) && - info1.overrides(info2, sym1.matchNullaryLoosely || sym2.matchNullaryLoosely, checkClassInfo = false) - - def handleDoubleDef = - if (preferSym(sym1, sym2)) denot1 - else if (preferSym(sym2, sym1)) denot2 - else doubleDefError(denot1, denot2, pre) - - if (sym2Accessible && prefer(sym2, sym1, info2, info1)) denot2 - else { - val sym1Accessible = sym1.isAccessibleFrom(pre) - if (sym1Accessible && prefer(sym1, sym2, info1, info2)) denot1 - else if (sym1Accessible && sym2.exists && !sym2Accessible) denot1 - else if (sym2Accessible && sym1.exists && !sym1Accessible) denot2 - else if (isDoubleDef(sym1, sym2)) handleDoubleDef - else { - val sym = - if (preferSym(sym2, sym1)) sym2 - else sym1 - val jointInfo = - try infoMeet(info1, info2, sym1, sym2, safeIntersection) - catch { - case ex: MergeError => - // TODO: this picks one type over the other whereas it might be better - // to return a MultiDenotation instead. But doing so would affect lots of - // things, starting with the return type of this method. - if (preferSym(sym2, sym1)) info2 - else if (preferSym(sym1, sym2)) info1 - else if (pre.widen.classSymbol.is(Scala2x) || migrateTo3) - info1 // follow Scala2 linearization - - // compare with way merge is performed in SymDenotation#computeMembersNamed - else throw new MergeError(ex.sym1, ex.sym2, ex.tp1, ex.tp2, pre) - } - new JointRefDenotation(sym, jointInfo, denot1.validFor & denot2.validFor, pre) - } - } - } + def isHidden(sym: Symbol) = sym.exists && !sym.isAccessibleFrom(pre) + val hidden1 = isHidden(sym1) + val hidden2 = isHidden(sym2) + if hidden1 && !hidden2 then denot2 + else if hidden2 && !hidden1 then denot1 + else + // The score that determines which symbol to pick for the result denotation. + // A value > 0 means pick `sym1`, < 0 means pick `sym2`. + // A value of +/- 2 means pick one of the denotations as a tie-breaker + // if a common info does not exist. + val symScore: Int = + if !sym1.exists then -2 + else if !sym2.exists then 2 + else if sym1.is(Bridge) && !sym2.is(Bridge) then -2 + else if sym2.is(Bridge) && !sym1.is(Bridge) then 2 + else if !sym1.isAsConcrete(sym2) then -1 + else if !sym2.isAsConcrete(sym1) then 1 + else + val linScore = linearScore(sym1.owner, sym2.owner) + if linScore != 0 then linScore + else + val boundary1 = accessBoundary(sym1) + val boundary2 = accessBoundary(sym2) + if boundary1.isProperlyContainedIn(boundary2) then -1 + else if boundary2.isProperlyContainedIn(boundary1) then 1 + else if sym2.is(Method) && !sym1.is(Method) then -1 + else if sym1.is(Method) && !sym2.is(Method) then 1 + else 0 + + val matchLoosely = sym1.matchNullaryLoosely || sym2.matchNullaryLoosely + + if symScore <= 0 && info2.overrides(info1, matchLoosely, checkClassInfo = false) then + denot2 + else if symScore >= 0 && info1.overrides(info2, matchLoosely, checkClassInfo = false) then + denot1 + else + val jointInfo = infoMeet(info1, info2, safeIntersection) + if jointInfo.exists then + val sym = if symScore >= 0 then sym1 else sym2 + JointRefDenotation(sym, jointInfo, denot1.validFor & denot2.validFor, pre) + else if symScore == 2 then denot1 + else if symScore == -2 then denot2 + else + overload.println(i"overloaded with same signature: ${sym1.showLocated}: $info1 / ${sym2.showLocated}: $info2, info = ${info1.getClass}, ${info2.getClass}, $jointInfo") + MultiDenotation(denot1, denot2) + end mergeSingleDenot if (this eq that) this else if (!this.exists) that @@ -508,64 +493,7 @@ object Denotations { val r = mergeDenot(this, that) if (r.exists) r else MultiDenotation(this, that) case that @ MultiDenotation(denot1, denot2) => - this & (denot1, pre) & (denot2, pre) - } - } - - /** Form a choice between this denotation and that one. - * @param pre The prefix type of the members of the denotation, used - * to determine an accessible symbol if it exists. - */ - def | (that: Denotation, pre: Type)(implicit ctx: Context): Denotation = { - - def unionDenot(denot1: SingleDenotation, denot2: SingleDenotation): Denotation = - if (denot1.matches(denot2)) { - val sym1 = denot1.symbol - val sym2 = denot2.symbol - val info1 = denot1.info - val info2 = denot2.info - val sameSym = sym1 eq sym2 - if (sameSym && (info1.widenExpr frozen_<:< info2.widenExpr)) denot2 - else if (sameSym && (info2.widenExpr frozen_<:< info1.widenExpr)) denot1 - else { - val jointSym = - if (sameSym) sym1 - else { - val owner2 = if (sym2 ne NoSymbol) sym2.owner else NoSymbol - /** Determine a symbol which is overridden by both sym1 and sym2. - * Preference is given to accessible symbols. - */ - def lubSym(overrides: Iterator[Symbol], previous: Symbol): Symbol = - if (!overrides.hasNext) previous - else { - val candidate = overrides.next() - if (owner2 derivesFrom candidate.owner) - if (candidate isAccessibleFrom pre) candidate - else lubSym(overrides, previous orElse candidate) - else - lubSym(overrides, previous) - } - lubSym(sym1.allOverriddenSymbols, NoSymbol) - } - new JointRefDenotation( - jointSym, infoJoin(info1, info2, sym1, sym2), denot1.validFor & denot2.validFor, pre) - } - } - else NoDenotation - - if (this eq that) this - else if (!this.exists) this - else if (!that.exists) that - else this match { - case denot1 @ MultiDenotation(denot11, denot12) => - denot1.derivedUnionDenotation(denot11 | (that, pre), denot12 | (that, pre)) - case denot1: SingleDenotation => - that match { - case denot2 @ MultiDenotation(denot21, denot22) => - denot2.derivedUnionDenotation(this | (denot21, pre), this | (denot22, pre)) - case denot2: SingleDenotation => - unionDenot(denot1, denot2) - } + this.meet(denot1, pre).meet(denot2, pre) } } @@ -580,11 +508,7 @@ object Denotations { final def containsSym(sym: Symbol): Boolean = hasUniqueSym && (symbol eq sym) } - // ------ Info meets and joins --------------------------------------------- - - /** Handle merge conflict by throwing a `MergeError` exception */ - private def mergeConflict(sym1: Symbol, sym2: Symbol, tp1: Type, tp2: Type)(implicit ctx: Context): Type = - throw new MergeError(sym1, sym2, tp1, tp2, NoPrefix) + // ------ Info meets ---------------------------------------------------- /** Merge parameter names of lambda types. If names in corresponding positions match, keep them, * otherwise generate new synthetic names. @@ -593,134 +517,55 @@ object Denotations { (for ((name1, name2, idx) <- tp1.paramNames.lazyZip(tp2.paramNames).lazyZip(tp1.paramNames.indices)) yield if (name1 == name2) name1 else tp1.companion.syntheticParamName(idx)).toList - /** Normally, `tp1 & tp2`. - * Special cases for matching methods and classes, with - * the possibility of raising a merge error. - * Special handling of ExprTypes, where mixed intersections widen the ExprType away. - */ - def infoMeet(tp1: Type, tp2: Type, sym1: Symbol, sym2: Symbol, safeIntersection: Boolean)(implicit ctx: Context): Type = - if (tp1 eq tp2) tp1 - else tp1 match { + /** Normally, `tp1 & tp2`, with extra care taken to return `tp1` or `tp2` directly if that's + * a valid answer. Special cases for matching methods and classes, with + * the possibility of returning NoType. Special handling of ExprTypes, where mixed + * intersections widen the ExprType away. + */ + def infoMeet(tp1: Type, tp2: Type, safeIntersection: Boolean)(implicit ctx: Context): Type = + if tp1 eq tp2 then tp1 + else tp1 match case tp1: TypeBounds => - tp2 match { - case tp2: TypeBounds => if (safeIntersection) tp1 safe_& tp2 else tp1 & tp2 + tp2 match + case tp2: TypeBounds => if safeIntersection then tp1 safe_& tp2 else tp1 & tp2 case tp2: ClassInfo => tp2 - case _ => mergeConflict(sym1, sym2, tp1, tp2) - } + case _ => NoType case tp1: ClassInfo => - tp2 match { + tp2 match case tp2: ClassInfo if tp1.cls eq tp2.cls => tp1.derivedClassInfo(tp1.prefix & tp2.prefix) case tp2: TypeBounds => tp1 - case _ => mergeConflict(sym1, sym2, tp1, tp2) - } - - // Two remedial strategies: - // - // 1. Prefer method types over poly types. This is necessary to handle - // overloaded definitions like the following - // - // def ++ [B >: A](xs: C[B]): D[B] - // def ++ (xs: C[A]): D[A] - // - // (Code like this is found in the collection strawman) - // - // 2. In the case of two method types or two polytypes with matching - // parameters and implicit status, merge corresponding parameter - // and result types. + case _ => NoType case tp1: MethodType => - tp2 match { - case tp2: PolyType => - tp1 + tp2 match case tp2: MethodType - if ctx.typeComparer.matchingMethodParams(tp1, tp2) && (tp1.companion eq tp2.companion) => - tp1.derivedLambdaType( - mergeParamNames(tp1, tp2), - tp1.paramInfos, - infoMeet(tp1.resultType, tp2.resultType.subst(tp2, tp1), sym1, sym2, safeIntersection)) - case _ => - mergeConflict(sym1, sym2, tp1, tp2) - } + if ctx.typeComparer.matchingMethodParams(tp1, tp2) + && (tp1.isImplicitMethod || tp1.isContextualMethod) == (tp2.isImplicitMethod || tp2.isContextualMethod) + && tp1.isErasedMethod == tp2.isErasedMethod => + val resType = infoMeet(tp1.resType, tp2.resType.subst(tp2, tp1), safeIntersection) + if resType.exists then + tp1.derivedLambdaType(mergeParamNames(tp1, tp2), tp1.paramInfos, resType) + else NoType + case _ => NoType case tp1: PolyType => - tp2 match { - case tp2: MethodType => - tp2 - case tp2: PolyType if ctx.typeComparer.matchingPolyParams(tp1, tp2) => - tp1.derivedLambdaType( - mergeParamNames(tp1, tp2), - tp1.paramInfos.zipWithConserve(tp2.paramInfos) { (p1, p2) => - infoMeet(p1, p2.subst(tp2, tp1), sym1, sym2, safeIntersection).bounds - }, - infoMeet(tp1.resultType, tp2.resultType.subst(tp2, tp1), sym1, sym2, safeIntersection)) - case _ => - mergeConflict(sym1, sym2, tp1, tp2) - } + tp2 match + case tp2: PolyType if sameLength(tp1.paramNames, tp2.paramNames) => + val resType = infoMeet(tp1.resType, tp2.resType.subst(tp2, tp1), safeIntersection) + if resType.exists then + tp1.derivedLambdaType( + mergeParamNames(tp1, tp2), + tp1.paramInfos.zipWithConserve(tp2.paramInfos)( _ & _ ), + resType) + else NoType + case _ => NoType case ExprType(rtp1) => - tp2 match { + tp2 match case ExprType(rtp2) => ExprType(rtp1 & rtp2) - case _ => infoMeet(rtp1, tp2, sym1, sym2, safeIntersection) - } + case _ => infoMeet(rtp1, tp2, safeIntersection) case _ => tp2 match - case _: MethodType | _: PolyType => - mergeConflict(sym1, sym2, tp1, tp2) - case _ => - try tp1 & tp2.widenExpr - catch - case ex: Throwable => - println(i"error for meet: $tp1 &&& $tp2, ${tp1.getClass}, ${tp2.getClass}") - throw ex - } - - /** Normally, `tp1 | tp2`. - * Special cases for matching methods and classes, with - * the possibility of raising a merge error. - * Special handling of ExprTypes, where mixed unions widen the ExprType away. - */ - def infoJoin(tp1: Type, tp2: Type, sym1: Symbol, sym2: Symbol)(implicit ctx: Context): Type = tp1 match { - case tp1: TypeBounds => - tp2 match { - case tp2: TypeBounds => tp1 | tp2 - case tp2: ClassInfo if tp1 contains tp2 => tp1 - case _ => mergeConflict(sym1, sym2, tp1, tp2) - } - case tp1: ClassInfo => - tp2 match { - case tp2: ClassInfo if tp1.cls eq tp2.cls => tp1.derivedClassInfo(tp1.prefix | tp2.prefix) - case tp2: TypeBounds if tp2 contains tp1 => tp2 - case _ => mergeConflict(sym1, sym2, tp1, tp2) - } - case tp1: MethodType => - tp2 match { - case tp2: MethodType - if ctx.typeComparer.matchingMethodParams(tp1, tp2) && (tp1.companion eq tp2.companion) => - tp1.derivedLambdaType( - mergeParamNames(tp1, tp2), - tp1.paramInfos, - infoJoin(tp1.resultType, tp2.resultType.subst(tp2, tp1), sym1, sym2)) - case _ => - mergeConflict(sym1, sym2, tp1, tp2) - } - case tp1: PolyType => - tp2 match { - case tp2: PolyType - if ctx.typeComparer.matchingPolyParams(tp1, tp2) => - tp1.derivedLambdaType( - mergeParamNames(tp1, tp2), - tp1.paramInfos.zipWithConserve(tp2.paramInfos) { (p1, p2) => - infoJoin(p1, p2.subst(tp2, tp1), sym1, sym2).bounds - }, - infoJoin(tp1.resultType, tp2.resultType.subst(tp2, tp1), sym1, sym2)) - case _ => - mergeConflict(sym1, sym2, tp1, tp2) - } - case ExprType(rtp1) => - tp2 match { - case ExprType(rtp2) => ExprType(rtp1 | rtp2) - case _ => rtp1 | tp2 - } - case _ => - tp1 | tp2.widenExpr - } + case _: MethodType | _: PolyType => NoType + case _ => tp1 & tp2.widenExpr + end infoMeet /** A non-overloaded denotation */ abstract class SingleDenotation(symbol: Symbol, initInfo: Type) extends Denotation(symbol, initInfo) { @@ -1237,20 +1082,6 @@ object Denotations { (sym1 `ne` sym2) && (sym1.effectiveOwner `eq` sym2.effectiveOwner) && !sym1.is(Bridge) && !sym2.is(Bridge)) - def doubleDefError(denot1: Denotation, denot2: Denotation, pre: Type = NoPrefix)(implicit ctx: Context): Nothing = { - val sym1 = denot1.symbol - val sym2 = denot2.symbol - if (denot1.isTerm) - throw new MergeError(sym1, sym2, sym1.info, sym2.info, pre) { - override def addendum(implicit ctx: Context) = - i""" - |they are both defined in ${this.sym1.effectiveOwner} but have matching signatures - | ${denot1.info} and - | ${denot2.info}${super.addendum}""" - } - else throw new MergeError(sym1, sym2, denot1.info, denot2.info, pre) - } - // --- Overloaded denotations and predenotations ------------------------------------------------- trait MultiPreDenotation extends PreDenotation { @@ -1280,7 +1111,7 @@ object Denotations { final case class DenotUnion(denot1: PreDenotation, denot2: PreDenotation) extends MultiPreDenotation { def exists: Boolean = true def toDenot(pre: Type)(implicit ctx: Context): Denotation = - denot1.toDenot(pre).&(denot2.toDenot(pre), pre) + denot1.toDenot(pre).meet(denot2.toDenot(pre), pre) def containsSym(sym: Symbol): Boolean = (denot1 containsSym sym) || (denot2 containsSym sym) type AsSeenFromResult = PreDenotation @@ -1307,10 +1138,12 @@ object Denotations { def suchThat(p: Symbol => Boolean)(implicit ctx: Context): SingleDenotation = { val sd1 = denot1.suchThat(p) val sd2 = denot2.suchThat(p) - if (sd1.exists) - if (sd2.exists) - if (isDoubleDef(denot1.symbol, denot2.symbol)) doubleDefError(denot1, denot2) - else throw new TypeError(i"failure to disambiguate overloaded reference at $this") + if sd1.exists then + if sd2.exists then + throw TypeError( + em"""Failure to disambiguate overloaded reference with + | ${denot1.symbol.showLocated}: ${denot1.info} and + | ${denot2.symbol.showLocated}: ${denot2.info}""") else sd1 else sd2 } diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index b3f952e292a2..e32ed23bc911 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -922,8 +922,8 @@ object SymDenotations { else true } - if (pre eq NoPrefix) true - else if (isAbsent()) false + if pre eq NoPrefix then true + else if isAbsent() then false else { val boundary = accessBoundary(owner) @@ -2100,7 +2100,7 @@ object SymDenotations { var names = Set[Name]() def maybeAdd(name: Name) = if (keepOnly(thisType, name)) names += name try { - for (p <- classParents) + for (p <- classParents if p.classSymbol.isClass) for (name <- p.classSymbol.asClass.memberNames(keepOnly)) maybeAdd(name) val ownSyms = diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 9d21925c6a77..380d0be7f613 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2049,16 +2049,6 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w * instantiated TypeVars are dereferenced and annotations are stripped. * Finally, refined types with the same refined name are * opportunistically merged. - * - * Sometimes, the conjunction of two types cannot be formed because - * the types are in conflict of each other. In particular: - * - * 1. Two different class types are conflicting. - * 2. A class type conflicts with a type bounds that does not include the class reference. - * 3. Two method or poly types with different (type) parameters but the same - * signature are conflicting - * - * In these cases, a MergeError is thrown. */ final def andType(tp1: Type, tp2: Type, isErased: Boolean = ctx.erasedTypes): Type = andTypeGen(tp1, tp2, AndType(_, _), isErased = isErased) @@ -2072,10 +2062,6 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w * ExprType, LambdaType). Also, when forming an `|`, * instantiated TypeVars are dereferenced and annotations are stripped. * - * Sometimes, the disjunction of two types cannot be formed because - * the types are in conflict of each other. (@see `andType` for an enumeration - * of these cases). In cases of conflict a `MergeError` is raised. - * * @param isErased Apply erasure semantics. If erased is true, instead of creating * an OrType, the lub will be computed using TypeCreator#erasedLub. */ @@ -2141,13 +2127,11 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w // gives =:= types), but it keeps the type smaller. tp2 match { case tp2: RefinedType if tp1.refinedName == tp2.refinedName => - try { - val jointInfo = Denotations.infoMeet(tp1.refinedInfo, tp2.refinedInfo, NoSymbol, NoSymbol, safeIntersection = false) + val jointInfo = Denotations.infoMeet(tp1.refinedInfo, tp2.refinedInfo, safeIntersection = false) + if jointInfo.exists then tp1.derivedRefinedType(tp1.parent & tp2.parent, tp1.refinedName, jointInfo) - } - catch { - case ex: MergeError => NoType - } + else + NoType case _ => NoType } diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index b335cf0695a5..24db641b7fca 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -163,32 +163,3 @@ object CyclicReference { } } -class MergeError(val sym1: Symbol, val sym2: Symbol, val tp1: Type, val tp2: Type, prefix: Type) extends TypeError { - - private def showSymbol(sym: Symbol)(implicit ctx: Context): String = - if (sym.exists) sym.showLocated else "[unknown]" - - private def showType(tp: Type)(implicit ctx: Context) = tp match { - case ClassInfo(_, cls, _, _, _) => cls.showLocated - case _ => tp.show - } - - protected def addendum(implicit ctx: Context): String = - if (prefix `eq` NoPrefix) "" - else { - val owner = prefix match { - case prefix: ThisType => prefix.cls.show - case prefix: TermRef => prefix.symbol.show - case _ => i"type $prefix" - } - s"\nas members of $owner" - } - - override def produceMessage(implicit ctx: Context): Message = { - if (ctx.debug) printStackTrace() - i"""cannot merge - | ${showSymbol(sym1)} of type ${showType(tp1)} and - | ${showSymbol(sym2)} of type ${showType(tp2)}$addendum - """ - } -} diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 9aa181bd2b02..031658e62409 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -675,7 +675,7 @@ object Types { pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, jointInfo) } else - val joint = pdenot & ( + val joint = pdenot.meet( new JointRefDenotation(NoSymbol, rinfo, Period.allInRun(ctx.runId), pre), pre, safeIntersection = ctx.base.pendingMemberSearches.contains(name)) @@ -726,7 +726,7 @@ object Types { } def goAnd(l: Type, r: Type) = - go(l) & (go(r), pre, safeIntersection = ctx.base.pendingMemberSearches.contains(name)) + go(l).meet(go(r), pre, safeIntersection = ctx.base.pendingMemberSearches.contains(name)) def goOr(tp: OrType) = tp match { case OrUncheckedNull(tp1) => diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala index 32b2f1d582b8..77442ead9298 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala @@ -82,7 +82,7 @@ class TastyPrinter(bytes: Array[Byte])(implicit ctx: Context) { printName(); printName() case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | NAMEDARG | BIND => printName(); printTrees() - case REFINEDtype | TERMREFin | TYPEREFin => + case REFINEDtype | TERMREFin | TYPEREFin | SELECTin => printName(); printTree(); printTrees() case RETURN | HOLE => printNat(); printTrees() diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 95dbaeb10267..7f275cec0f03 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -9,6 +9,7 @@ import dotty.tools.tasty.TastyBuffer._ import ast.Trees._ import ast.{untpd, tpd} import Contexts._, Symbols._, Types._, Names._, Constants._, Decorators._, Annotations._, Flags._ +import Denotations.MultiDenotation import typer.Inliner import NameKinds._ import StdNames.nme @@ -173,22 +174,20 @@ class TreePickler(pickler: TastyPickler) { case tpe: NamedType => val sym = tpe.symbol def pickleExternalRef(sym: Symbol) = { - def pickleCore() = { - pickleNameAndSig(sym.name, tpe.signature) - pickleType(tpe.prefix) - } val isShadowedRef = sym.isClass && tpe.prefix.member(sym.name).symbol != sym if (sym.is(Flags.Private) || isShadowedRef) { writeByte(if (tpe.isType) TYPEREFin else TERMREFin) withLength { - pickleCore() + pickleNameAndSig(sym.name, tpe.symbol.signature) + pickleType(tpe.prefix) pickleType(sym.owner.typeRef) } } else { writeByte(if (tpe.isType) TYPEREF else TERMREF) - pickleCore() + pickleNameAndSig(sym.name, tpe.signature) + pickleType(tpe.prefix) } } if (sym.is(Flags.Package)) { @@ -381,10 +380,23 @@ class TreePickler(pickler: TastyPickler) { pickleType(tp) } case _ => - writeByte(if (name.isTypeName) SELECTtpt else SELECT) val sig = tree.tpe.signature - pickleNameAndSig(name, sig) - pickleTree(qual) + val isAmbiguous = + sig != Signature.NotAMethod + && qual.tpe.nonPrivateMember(name).match + case d: MultiDenotation => d.atSignature(sig).isInstanceOf[MultiDenotation] + case _ => false + if isAmbiguous then + writeByte(SELECTin) + withLength { + pickleNameAndSig(name, tree.symbol.signature) + pickleTree(qual) + pickleType(tree.symbol.owner.typeRef) + } + else + writeByte(if (name.isTypeName) SELECTtpt else SELECT) + pickleNameAndSig(name, sig) + pickleTree(qual) } case Apply(fun, args) => if (fun.symbol eq defn.throwMethod) { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index f8adb6426a7d..75b29cd4d78f 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -9,6 +9,7 @@ import Symbols._ import Types._ import Scopes._ import SymDenotations._ +import Denotations._ import Names._ import NameOps._ import StdNames._ @@ -330,12 +331,12 @@ class TreeUnpickler(reader: TastyReader, case TERMREFin => var sname = readName() val prefix = readType() - val space = readType() + val owner = readType() sname match { case SignedName(name, sig) => - TermRef(prefix, name, space.decl(name).asSeenFrom(prefix).atSignature(sig)) + TermRef(prefix, name, owner.decl(name).atSignature(sig).asSeenFrom(prefix)) case name => - TermRef(prefix, name, space.decl(name).asSeenFrom(prefix)) + TermRef(prefix, name, owner.decl(name).asSeenFrom(prefix)) } case TYPEREFin => val name = readName().toTypeName @@ -1040,10 +1041,8 @@ class TreeUnpickler(reader: TastyReader, } } - def completeSelect(name: Name, sig: Signature): Select = { - val qual = readTerm()(ctx) + def makeSelect(qual: Tree, name: Name, denot: Denotation): Select = var qualType = qual.tpe.widenIfUnstable - val denot = accessibleDenot(qualType, name, sig) val owner = denot.symbol.maybeOwner if (owner.isPackageObject && qualType.termSymbol.is(Package)) qualType = qualType.select(owner.sourceModule) @@ -1052,7 +1051,11 @@ class TreeUnpickler(reader: TastyReader, case name: TermName => TermRef(qualType, name, denot) } ConstFold(untpd.Select(qual, name).withType(tpe)) - } + + def completeSelect(name: Name, sig: Signature): Select = + val qual = readTerm()(ctx) + val denot = accessibleDenot(qual.tpe.widenIfUnstable, name, sig) + makeSelect(qual, name, denot) def readQualId(): (untpd.Ident, TypeRef) = val qual = readTerm().asInstanceOf[untpd.Ident] @@ -1165,6 +1168,18 @@ class TreeUnpickler(reader: TastyReader, case SELECTouter => val levels = readNat() readTerm().outerSelect(levels, SkolemType(readType())) + case SELECTin => + var sname = readName() + val qual = readTerm() + val owner = readType() + def select(name: Name, denot: Denotation) = + val prefix = ctx.typeAssigner.maybeSkolemizePrefix(qual.tpe.widenIfUnstable, name) + makeSelect(qual, name, denot.asSeenFrom(prefix)) + sname match + case SignedName(name, sig) => + select(name, owner.decl(name).atSignature(sig)) + case name => + select(name, owner.decl(name)) case REPEATED => val elemtpt = readTpt() SeqLiteral(until(end)(readTerm()), elemtpt) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 7e08fd2522ac..41f720a9ae67 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -425,22 +425,10 @@ object RefChecks { }*/ } - try { - val opc = new OverridingPairs.Cursor(clazz) - while (opc.hasNext) { - checkOverride(opc.overriding, opc.overridden) - opc.next() - } - } - catch { - case ex: MergeError => - val addendum = ex.tp1 match { - case tp1: ClassInfo => - "\n(Note that having same-named member classes in types of a mixin composition is no longer allowed)" - case _ => "" - } - ctx.error(ex.getMessage + addendum, clazz.sourcePos) - } + val opc = new OverridingPairs.Cursor(clazz) + while opc.hasNext do + checkOverride(opc.overriding, opc.overridden) + opc.next() printMixinOverrideErrors() // Verifying a concrete class has nothing unimplemented. diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 96ded6aa7df9..61976af89d96 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -75,6 +75,7 @@ Standard-Section: "ASTs" TopLevelStat* Term = Path -- Paths represent both types and terms IDENT NameRef Type -- Used when term ident’s type is not a TermRef SELECT possiblySigned_NameRef qual_Term -- qual.name + SELECTin Length possiblySigned_NameRef qual_Term owner_Type -- qual.name, referring to a symbol declared in owner that has the given signature (see note below) QUALTHIS typeIdent_Tree -- id.this, different from THIS in that it contains a qualifier ident with position. NEW clsType_Term -- new cls THROW throwableExpr_Term -- throw throwableExpr @@ -122,7 +123,7 @@ Standard-Section: "ASTs" TopLevelStat* TERMREFsymbol sym_ASTRef qual_Type -- A reference `qual.sym` to a local member with prefix `qual` TERMREFpkg fullyQualified_NameRef -- A reference to a package member with given fully qualified name TERMREF possiblySigned_NameRef qual_Type -- A reference `qual.name` to a non-local member - TERMREFin Length possiblySigned_NameRef qual_Type namespace_Type -- A reference `qual.name` to a non-local member that's private in `namespace` + TERMREFin Length possiblySigned_NameRef qual_Type owner_Type -- A reference `qual.name` referring to a non-local symbol declared in owner that has the given signature (see note below) THIS clsRef_Type -- cls.this RECthis recType_ASTRef -- The `this` in a recursive refined type `recType`. SHAREDtype path_ASTRef -- link to previously serialized path @@ -212,6 +213,10 @@ Standard-Section: "ASTs" TopLevelStat* Annotation = ANNOTATION Length tycon_Type fullAnnotation_Term -- An annotation, given (class) type of constructor, and full application tree +Note: The signature of a SELECTin or TERMREFin node is the signature of the selected symbol, + not the signature of the reference. The latter undergoes an asSeenFrom but the former + does not. + 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 @@ -248,7 +253,7 @@ Standard Section: "Comments" Comment* object TastyFormat { final val header: Array[Int] = Array(0x5C, 0xA1, 0xAB, 0x1F) - val MajorVersion: Int = 22 + val MajorVersion: Int = 23 val MinorVersion: Int = 0 /** Tags used to serialize names, should update [[nameTagToString]] if a new constant is added */ @@ -452,6 +457,7 @@ object TastyFormat { final val ANNOTATION = 173 final val TERMREFin = 174 final val TYPEREFin = 175 + final val SELECTin = 176 final val METHODtype = 180 @@ -646,6 +652,7 @@ object TastyFormat { case SUPERtype => "SUPERtype" case TERMREFin => "TERMREFin" case TYPEREFin => "TYPEREFin" + case SELECTin => "SELECTin" case REFINEDtype => "REFINEDtype" case REFINEDtpt => "REFINEDtpt" @@ -675,7 +682,7 @@ object TastyFormat { */ def numRefs(tag: Int): Int = tag match { case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | NAMEDARG | RETURN | BIND | - SELFDEF | REFINEDtype | TERMREFin | TYPEREFin | HOLE => 1 + SELFDEF | REFINEDtype | TERMREFin | TYPEREFin | SELECTin | HOLE => 1 case RENAMED | PARAMtype => 2 case POLYtype | TYPELAMBDAtype | METHODtype => -1 case _ => 0 diff --git a/tests/neg-custom-args/allow-double-bindings/i1240.scala b/tests/neg-custom-args/allow-double-bindings/i1240.scala index 3f4d1e210592..5306f2c6ad20 100644 --- a/tests/neg-custom-args/allow-double-bindings/i1240.scala +++ b/tests/neg-custom-args/allow-double-bindings/i1240.scala @@ -8,7 +8,7 @@ class C[T] { object C { def main(args: Array[String]) = - new C[D]().foo(new D()) // error: ambiguous + new C[D]().foo(new D()) } class C1[T] { diff --git a/tests/neg/i1240b.scala b/tests/neg/i1240b.scala index 8f2ec81d4702..2d23db61470b 100644 --- a/tests/neg/i1240b.scala +++ b/tests/neg/i1240b.scala @@ -9,4 +9,4 @@ abstract class A[X] extends T[X] { trait U[X] extends T[X] { abstract override def foo(x: X): X = super.foo(x) } -object Test extends A[String] with U[String] // error: accidental override // error: merge error +object Test extends A[String] with U[String] // error: accidental override diff --git a/tests/neg/i4470a.scala b/tests/neg/i4470a.scala index 001b286a1d74..1733a3e9b6fe 100644 --- a/tests/neg/i4470a.scala +++ b/tests/neg/i4470a.scala @@ -1,7 +1,7 @@ object RepeatedEnum { enum Maybe { // error - case Foo // error + case Foo } enum Maybe { // error diff --git a/tests/neg/i4470b.scala b/tests/neg/i4470b.scala index 7305f1018cbe..68fad4cd2a7e 100644 --- a/tests/neg/i4470b.scala +++ b/tests/neg/i4470b.scala @@ -1,6 +1,6 @@ object RepeatedExtendEnum { - enum Maybe[T] derives Eql { // error // error + enum Maybe[T] derives Eql { // error case Foo extends Maybe[Int] } diff --git a/tests/neg/i4470c.scala b/tests/neg/i4470c.scala index a77d185818ee..82e4a1ffa4db 100644 --- a/tests/neg/i4470c.scala +++ b/tests/neg/i4470c.scala @@ -1,6 +1,6 @@ object DuplicatedEnum { enum Maybe[+T] { // error - case Some(x: T) // error + case Some(x: T) } enum Maybe[+T] { // error diff --git a/tests/neg/i7425.scala b/tests/neg/i7425.scala index 24481ea375f4..961b3f0f855a 100644 --- a/tests/neg/i7425.scala +++ b/tests/neg/i7425.scala @@ -2,11 +2,11 @@ class C { def f: Int = 0 } trait D { def f(): Int = 0 } -def foo1(x: C & D) = x.f // error: method f must be called with argument +def foo1(x: C & D) = x.f // ok def foo2(x: C | D) = x.f // error: value f is not a member of C | D def foo3(x: D & C) = x.f // ok def foo4(x: D | C) = x.f // error: value f is not a member of D | C def foo5(x: C & D) = x.f() // ok def foo6(x: C | D) = x.f() // error: value f is not a member of C | D -def foo7(x: D & C) = x.f() // error: method f in class C does not take parameters +def foo7(x: D & C) = x.f() // ok def foo8(x: D | C) = x.f() // error: value f is not a member of D | C diff --git a/tests/neg/mixin-class-member-clash.scala b/tests/neg/mixin-class-member-clash.scala new file mode 100644 index 000000000000..35c02a85c904 --- /dev/null +++ b/tests/neg/mixin-class-member-clash.scala @@ -0,0 +1,7 @@ +trait T { + class C +} +trait U { + class C +} +class C extends T, U // error: error overriding class C in trait T diff --git a/tests/pending/pos/cps-async-failure.scala b/tests/pending/pos/cps-async-failure.scala new file mode 100644 index 000000000000..aef801dad9e9 --- /dev/null +++ b/tests/pending/pos/cps-async-failure.scala @@ -0,0 +1,48 @@ + +import scala.quoted._ + +trait App[F[_],CT]: + this: Base[F,CT] => + + import qctx.tasty._ + + trait AA + + def f(cpsCtx: FC[F]): AA = + g(cpsCtx) + + def g(cpsCtx: FC[F]): AA = + val paramSym: Symbol = ??? + println(paramSym.tree) // println necessary for failure + val t: Term = ??? + t match { // match necessary for failure + case Typed(_, _) => ??? // both cases necessary for failure + case Lambda(_, _) => ??? + } + f(cpsCtx) // necessary for failure + val cpsBody = runRoot() // necessary for failure + g(cpsCtx) // failure + // 26 | g(cpsCtx) + // | ^ + // |Ambiguous overload. The overloaded alternatives of method g in trait App with types + // | (cpsCtx: FC[F]): Base.this.AA + // | (cpsCtx: FC[F]): App.this.AA + // |both match arguments ((cpsCtx : FC[F])) + // | + // |Note: this happens because two or more alternatives have the same erasure, + // | so they cannot be distinguished by overloading resolution + + +class FC[F[_]]() + +trait Base[F[_]:Type,CT:Type] // Both :Type context bounds are necessary for failure +extends Cps with Root[F, CT] with App[F, CT]: + implicit val qctx: QuoteContext + +trait Root[F[_], CT]: + this: Base[F, CT] => + def runRoot(): CpsTree = ??? + +trait Cps: + sealed abstract class CpsTree + diff --git a/tests/pos/i9050.scala b/tests/pos/i9050.scala new file mode 100644 index 000000000000..e538ec5d1cd3 --- /dev/null +++ b/tests/pos/i9050.scala @@ -0,0 +1,6 @@ + +object Foo { + val foo = scala.collection.mutable.ArrayBuffer.empty[Seq[Double]] + val bar = Seq.empty[Double] + foo.append(bar) +} \ No newline at end of file