Skip to content

Commit e82756a

Browse files
committed
Simplify implementation of & on denotations
1 parent 162f3fa commit e82756a

File tree

6 files changed

+85
-142
lines changed

6 files changed

+85
-142
lines changed

compiler/src/dotty/tools/dotc/core/Denotations.scala

Lines changed: 78 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ object Denotations {
385385
*
386386
* If there is no preferred accessible denotation, return a JointRefDenotation
387387
* with one of the operand symbols (unspecified which one), and an info which
388-
* is the intersection (using `&` or `safe_&` if `safeIntersection` is true)
388+
* is the intersection using `&` or `safe_&` if `safeIntersection` is true)
389389
* of the infos of the operand denotations.
390390
*/
391391
def & (that: Denotation, pre: Type, safeIntersection: Boolean = false)(implicit ctx: Context): Denotation = {
@@ -412,18 +412,21 @@ object Denotations {
412412
val sym1 = denot1.symbol
413413
val sym2 = denot2.symbol
414414

415-
val sym2Accessible = sym2.isAccessibleFrom(pre)
416-
417415
/** Does `sym1` come before `sym2` in the linearization of `pre`? */
418-
def precedes(sym1: Symbol, sym2: Symbol) = {
419-
def precedesIn(bcs: List[ClassSymbol]): Boolean = bcs match
420-
case bc :: bcs1 => (sym1 eq bc) || !(sym2 eq bc) && precedesIn(bcs1)
421-
case Nil => false
422-
423-
(sym1 ne sym2) &&
424-
(sym1.derivesFrom(sym2) ||
425-
!sym2.derivesFrom(sym1) && precedesIn(pre.baseClasses))
426-
}
416+
def linearScore(owner1: Symbol, owner2: Symbol): Int =
417+
418+
def searchBaseClasses(bcs: List[ClassSymbol]): Int = bcs match
419+
case bc :: bcs1 =>
420+
if bc eq owner1 then 1
421+
else if bc eq owner2 then -1
422+
else searchBaseClasses(bcs1)
423+
case Nil => 0
424+
425+
if owner1 eq owner2 then 0
426+
else if owner1.derivesFrom(owner2) then 1
427+
else if owner2.derivesFrom(owner1) then -1
428+
else searchBaseClasses(pre.baseClasses)
429+
end linearScore
427430

428431
/** Similar to SymDenotation#accessBoundary, but without the special cases. */
429432
def accessBoundary(sym: Symbol) =
@@ -432,82 +435,49 @@ object Denotations {
432435
if (sym.is(Protected)) sym.owner.enclosingPackageClass
433436
else defn.RootClass)
434437

435-
/** Establish a partial order "preference" order between symbols.
436-
* Give preference to `sym1` over `sym2` if one of the following
437-
* conditions holds, in decreasing order of weight:
438-
* 1. sym2 doesn't exist
439-
* 2. sym1 is concrete and sym2 is abstract
440-
* 3. The owner of sym1 comes before the owner of sym2 in the linearization
441-
* of the type of the prefix `pre`.
442-
* 4. The access boundary of sym2 is properly contained in the access
443-
* boundary of sym1. For protected access, we count the enclosing
444-
* package as access boundary.
445-
* 5. sym1 is a method but sym2 is not.
446-
* The aim of these criteria is to give some disambiguation on access which
447-
* - does not depend on textual order or other arbitrary choices
448-
* - minimizes raising of doubleDef errors
449-
*/
450-
def preferSym(sym1: Symbol, sym2: Symbol) =
451-
sym1.eq(sym2)
452-
|| sym1.exists
453-
&& (!sym2.exists
454-
|| sym1.isAsConcrete(sym2)
455-
&& (!sym2.isAsConcrete(sym1)
456-
|| precedes(sym1.owner, sym2.owner)
457-
|| accessBoundary(sym2).isProperlyContainedIn(accessBoundary(sym1))
458-
|| sym2.is(Bridge) && !sym1.is(Bridge)
459-
|| sym1.is(Method) && !sym2.is(Method))
460-
|| sym1.info.isErroneous)
461-
462-
/** Sym preference provided types also override */
463-
def prefer(sym1: Symbol, sym2: Symbol, info1: Type, info2: Type) =
464-
preferSym(sym1, sym2) &&
465-
info1.overrides(info2, sym1.matchNullaryLoosely || sym2.matchNullaryLoosely, checkClassInfo = false)
466-
467-
/** Handle conflict where we have either definitions coming from the same class
468-
* that have matching signatures as seen from the current prefix, or we have
469-
* definitions from different classes that have the same signature but
470-
* conflicting infos. In these cases, do a last minute disambiguation
471-
* by picking one alternative according to the ranking
472-
*
473-
* 1. Non-bridge methods
474-
* 2. non-methods
475-
* 3. bridges
476-
* 4. NoSymbol
477-
*
478-
* If that fails, return a MultiDenotation with both alternatives
479-
* and rely on overloading resolution to pick one of them.
480-
*/
481-
def handleConflict: Denotation =
482-
483-
def preferMethod(sym1: Symbol, sym2: Symbol): Boolean =
484-
sym1.exists &&
485-
(!sym2.exists
486-
|| sym2.is(Bridge) && !sym1.is(Bridge)
487-
|| sym1.is(Method) && !sym2.is(Method)
488-
|| sym1.info.isErroneous)
489-
490-
if preferMethod(sym1, sym2) then denot1
491-
else if preferMethod(sym2, sym1) then denot2
492-
else
493-
overload.println(i"overloaded with same signature: ${sym1.showLocated}: $info1 / ${sym2.showLocated}: $info2")
494-
MultiDenotation(denot1, denot2)
495-
end handleConflict
496-
497-
if (sym2Accessible && prefer(sym2, sym1, info2, info1)) denot2
438+
def isHidden(sym: Symbol) = sym.exists && !sym.isAccessibleFrom(pre)
439+
val hidden1 = isHidden(sym1)
440+
val hidden2 = isHidden(sym2)
441+
if hidden1 && !hidden2 then denot2
442+
else if hidden2 && !hidden1 then denot1
498443
else
499-
val sym1Accessible = sym1.isAccessibleFrom(pre)
500-
if (sym1Accessible && prefer(sym1, sym2, info1, info2)) denot1
501-
else if (sym1Accessible && sym2.exists && !sym2Accessible) denot1
502-
else if (sym2Accessible && sym1.exists && !sym1Accessible) denot2
503-
else if isDoubleDef(sym1, sym2) then handleConflict
444+
val symScore: Int =
445+
if !sym1.exists then -2
446+
else if !sym2.exists then 2
447+
else if !sym1.isAsConcrete(sym2) then -1
448+
else if !sym2.isAsConcrete(sym1) then 1
449+
else
450+
val linScore = linearScore(sym1.owner, sym2.owner)
451+
if linScore != 0 then linScore
452+
else
453+
val boundary1 = accessBoundary(sym1)
454+
val boundary2 = accessBoundary(sym2)
455+
if boundary1.isProperlyContainedIn(boundary2) then -1
456+
else if boundary2.isProperlyContainedIn(boundary1) then 1
457+
else if sym1.is(Bridge) && !sym2.is(Bridge) then -2
458+
else if sym2.is(Bridge) && !sym1.is(Bridge) then 2
459+
else if sym2.is(Method) && !sym1.is(Method) then -2
460+
else if sym1.is(Method) && !sym2.is(Method) then 2
461+
else 0
462+
463+
val matchLoosely = sym1.matchNullaryLoosely || sym2.matchNullaryLoosely
464+
465+
if info2.overrides(info1, matchLoosely, checkClassInfo = false)
466+
&& symScore <= 0
467+
then denot2
468+
else if info1.overrides(info2, matchLoosely, checkClassInfo = false)
469+
&& symScore >= 0
470+
then denot1
504471
else
505-
val sym = if preferSym(sym2, sym1) then sym2 else sym1
506472
val jointInfo = infoMeet(info1, info2, safeIntersection)
507473
if jointInfo.exists then
474+
val sym = if symScore > 0 then sym1 else sym2
508475
JointRefDenotation(sym, jointInfo, denot1.validFor & denot2.validFor, pre)
476+
else if symScore == 2 then denot1
477+
else if symScore == -2 then denot2
509478
else
510-
handleConflict
479+
overload.println(i"overloaded with same signature: ${sym1.showLocated}: $info1 / ${sym2.showLocated}: $info2, info = ${info1.getClass}, ${info2.getClass}, $jointInfo")
480+
MultiDenotation(denot1, denot2)
511481
end mergeSingleDenot
512482

513483
if (this eq that) this
@@ -533,7 +503,7 @@ object Denotations {
533503
final def containsSym(sym: Symbol): Boolean = hasUniqueSym && (symbol eq sym)
534504
}
535505

536-
// ------ Info meets and joins ---------------------------------------------
506+
// ------ Info meets ----------------------------------------------------
537507

538508
/** Merge parameter names of lambda types. If names in corresponding positions match, keep them,
539509
* otherwise generate new synthetic names.
@@ -542,82 +512,55 @@ object Denotations {
542512
(for ((name1, name2, idx) <- tp1.paramNames.lazyZip(tp2.paramNames).lazyZip(tp1.paramNames.indices))
543513
yield if (name1 == name2) name1 else tp1.companion.syntheticParamName(idx)).toList
544514

545-
/** Normally, `tp1 & tp2`
546-
* Special cases for matching methods and classes, with
547-
* the possibility of returning NoType.
548-
* Special handling of ExprTypes, where mixed intersections widen the ExprType away.
549-
*/
515+
/** Normally, `tp1 & tp2`, with extra care taken to return `tp1` or `tp2` directly if that's
516+
* a valid answer. Special cases for matching methods and classes, with
517+
* the possibility of returning NoType. Special handling of ExprTypes, where mixed
518+
* intersections widen the ExprType away.
519+
*/
550520
def infoMeet(tp1: Type, tp2: Type, safeIntersection: Boolean)(implicit ctx: Context): Type =
551-
if (tp1 eq tp2) tp1
552-
else tp1 match {
521+
if tp1 eq tp2 then tp1
522+
else tp1 match
553523
case tp1: TypeBounds =>
554-
tp2 match {
555-
case tp2: TypeBounds => if (safeIntersection) tp1 safe_& tp2 else tp1 & tp2
524+
tp2 match
525+
case tp2: TypeBounds => if safeIntersection then tp1 safe_& tp2 else tp1 & tp2
556526
case tp2: ClassInfo => tp2
557527
case _ => NoType
558-
}
559528
case tp1: ClassInfo =>
560-
tp2 match {
529+
tp2 match
561530
case tp2: ClassInfo if tp1.cls eq tp2.cls => tp1.derivedClassInfo(tp1.prefix & tp2.prefix)
562531
case tp2: TypeBounds => tp1
563532
case _ => NoType
564-
}
565-
566-
// Two remedial strategies:
567-
//
568-
// 1. Prefer method types over poly types. This is necessary to handle
569-
// overloaded definitions like the following
570-
//
571-
// def ++ [B >: A](xs: C[B]): D[B]
572-
// def ++ (xs: C[A]): D[A]
573-
//
574-
// (Code like this is found in the collection strawman)
575-
//
576-
// 2. In the case of two method types or two polytypes with matching
577-
// parameters and implicit status, merge corresponding parameter
578-
// and result types.
579533
case tp1: MethodType =>
580-
tp2 match {
534+
tp2 match
581535
case tp2: MethodType
582-
if ctx.typeComparer.matchingMethodParams(tp1, tp2) && (tp1.companion eq tp2.companion) =>
536+
if ctx.typeComparer.matchingMethodParams(tp1, tp2)
537+
&& (tp1.isImplicitMethod || tp1.isContextualMethod) == (tp2.isImplicitMethod || tp2.isContextualMethod)
538+
&& tp1.isErasedMethod == tp2.isErasedMethod =>
583539
val resType = infoMeet(tp1.resType, tp2.resType.subst(tp2, tp1), safeIntersection)
584540
if resType.exists then
585541
tp1.derivedLambdaType(mergeParamNames(tp1, tp2), tp1.paramInfos, resType)
586-
else
587-
NoType
588-
case _ =>
589-
NoType
590-
}
542+
else NoType
543+
case _ => NoType
591544
case tp1: PolyType =>
592-
tp2 match {
593-
case tp2: PolyType if ctx.typeComparer.matchingPolyParams(tp1, tp2) =>
545+
tp2 match
546+
case tp2: PolyType if sameLength(tp1.paramNames, tp2.paramNames) =>
594547
val resType = infoMeet(tp1.resType, tp2.resType.subst(tp2, tp1), safeIntersection)
595548
if resType.exists then
596549
tp1.derivedLambdaType(
597550
mergeParamNames(tp1, tp2),
598551
tp1.paramInfos.zipWithConserve(tp2.paramInfos)( _ & _ ),
599552
resType)
600-
else
601-
NoType
602-
case _ =>
603-
NoType
604-
}
553+
else NoType
554+
case _ => NoType
605555
case ExprType(rtp1) =>
606-
tp2 match {
556+
tp2 match
607557
case ExprType(rtp2) => ExprType(rtp1 & rtp2)
608558
case _ => infoMeet(rtp1, tp2, safeIntersection)
609-
}
610559
case _ =>
611560
tp2 match
612-
case _: MethodType | _: PolyType =>
613-
NoType
614-
case _ =>
615-
try tp1 & tp2.widenExpr
616-
catch
617-
case ex: Throwable =>
618-
println(i"error for meet: $tp1 &&& $tp2, ${tp1.getClass}, ${tp2.getClass}")
619-
throw ex
620-
}
561+
case _: MethodType | _: PolyType => NoType
562+
case _ => tp1 & tp2.widenExpr
563+
end infoMeet
621564

622565
/** A non-overloaded denotation */
623566
abstract class SingleDenotation(symbol: Symbol, initInfo: Type) extends Denotation(symbol, initInfo) {
@@ -1193,7 +1136,7 @@ object Denotations {
11931136
if sd1.exists then
11941137
if sd2.exists then
11951138
throw TypeError(
1196-
em"""Failure to disambiguate oberloaded reference with
1139+
em"""Failure to disambiguate overloaded reference with
11971140
| ${denot1.symbol.showLocated}: ${denot1.info} and
11981141
| ${denot2.symbol.showLocated}: ${denot2.info}""")
11991142
else sd1

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -922,8 +922,8 @@ object SymDenotations {
922922
else true
923923
}
924924

925-
if (pre eq NoPrefix) true
926-
else if (isAbsent()) false
925+
if pre eq NoPrefix then true
926+
else if isAbsent() then false
927927
else {
928928
val boundary = accessBoundary(owner)
929929

@@ -2100,7 +2100,7 @@ object SymDenotations {
21002100
var names = Set[Name]()
21012101
def maybeAdd(name: Name) = if (keepOnly(thisType, name)) names += name
21022102
try {
2103-
for (p <- classParents)
2103+
for (p <- classParents if p.classSymbol.isClass)
21042104
for (name <- p.classSymbol.asClass.memberNames(keepOnly))
21052105
maybeAdd(name)
21062106
val ownSyms =

tests/neg-custom-args/allow-double-bindings/i1240.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class C[T] {
88

99
object C {
1010
def main(args: Array[String]) =
11-
new C[D]().foo(new D()) // error: ambiguous
11+
new C[D]().foo(new D())
1212
}
1313

1414
class C1[T] {

tests/neg/i4470a.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
object RepeatedEnum {
22

33
enum Maybe { // error
4-
case Foo // error
4+
case Foo
55
}
66

77
enum Maybe { // error

tests/neg/i4470b.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
object RepeatedExtendEnum {
22

3-
enum Maybe[T] derives Eql { // error // error
3+
enum Maybe[T] derives Eql { // error
44
case Foo extends Maybe[Int]
55
}
66

tests/neg/i4470c.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
object DuplicatedEnum {
22
enum Maybe[+T] { // error
3-
case Some(x: T) // error
3+
case Some(x: T)
44
}
55

66
enum Maybe[+T] { // error

0 commit comments

Comments
 (0)