aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/dotty/tools/dotc/ast/tpd.scala3
-rw-r--r--src/dotty/tools/dotc/core/Denotations.scala42
-rw-r--r--src/dotty/tools/dotc/typer/Applications.scala158
-rw-r--r--tests/neg/i1212.scala1
-rw-r--r--tests/neg/patmat.scala24
-rw-r--r--tests/pos/hkgadt.scala9
-rw-r--r--tests/pos/i618.scala3
-rw-r--r--tests/pos/t2660.scala (renamed from tests/neg/t2660.scala)8
-rw-r--r--tests/run/i1386.scala4
-rw-r--r--tests/run/t1381.check7
-rw-r--r--tests/run/t1381.scala59
11 files changed, 228 insertions, 90 deletions
diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala
index 4593b9554..2b0e63a19 100644
--- a/src/dotty/tools/dotc/ast/tpd.scala
+++ b/src/dotty/tools/dotc/ast/tpd.scala
@@ -854,8 +854,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
var allAlts = denot.alternatives
.map(_.termRef).filter(tr => typeParamCount(tr) == targs.length)
if (targs.isEmpty) allAlts = allAlts.filterNot(_.widen.isInstanceOf[PolyType])
- val alternatives =
- ctx.typer.resolveOverloaded(allAlts, proto, Nil)
+ val alternatives = ctx.typer.resolveOverloaded(allAlts, proto)
assert(alternatives.size == 1,
i"${if (alternatives.isEmpty) "no" else "multiple"} overloads available for " +
i"$method on ${receiver.tpe.widenDealias} with targs: $targs%, %; args: $args%, % of types ${args.tpes}%, %; expectedType: $expectedType." +
diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala
index 09971d1d1..80daa9681 100644
--- a/src/dotty/tools/dotc/core/Denotations.scala
+++ b/src/dotty/tools/dotc/core/Denotations.scala
@@ -295,35 +295,65 @@ object Denotations {
val sym1 = denot1.symbol
val sym2 = denot2.symbol
- if (isDoubleDef(sym1, sym2)) doubleDefError(denot1, denot2, pre)
-
val sym2Accessible = sym2.isAccessibleFrom(pre)
+
/** 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 => true
}
- sym1.derivesFrom(sym2) ||
- !sym2.derivesFrom(sym1) && precedesIn(pre.baseClasses)
+ (sym1 ne sym2) &&
+ (sym1.derivesFrom(sym2) ||
+ !sym2.derivesFrom(sym1) && precedesIn(pre.baseClasses))
}
- /** Preference according to partial pre-order (isConcrete, precedes) */
+ /** Similar to SymDenotation#accessBoundary, but without the special cases. */
+ def accessBoundary(sym: Symbol) =
+ if (sym.is(Private)) sym.owner
+ else sym.privateWithin.orElse(
+ 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. sym1 is concrete and sym2 is abstract
+ * 2. The owner of sym1 comes before the owner of sym2 in the linearization
+ * of the type of the prefix `pre`.
+ * 3. 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.
+ * 4. sym1 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.isAsConcrete(sym2) &&
- (!sym2.isAsConcrete(sym1) || precedes(sym1.owner, sym2.owner))
+ (!sym2.isAsConcrete(sym1) ||
+ precedes(sym1.owner, sym2.owner) ||
+ accessBoundary(sym2).isProperlyContainedIn(accessBoundary(sym1)) ||
+ sym1.is(Method) && !sym2.is(Method))
/** Sym preference provided types also override */
def prefer(sym1: Symbol, sym2: Symbol, info1: Type, info2: Type) =
preferSym(sym1, sym2) && info1.overrides(info2)
+ 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 (!sym1.exists) sym2
diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala
index 6e78a570d..69ad4f107 100644
--- a/src/dotty/tools/dotc/typer/Applications.scala
+++ b/src/dotty/tools/dotc/typer/Applications.scala
@@ -740,13 +740,19 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
def fromScala2x = unapplyFn.symbol.exists && (unapplyFn.symbol.owner is Scala2x)
- /** Can `subtp` be made to be a subtype of `tp`, possibly by dropping some
- * refinements in `tp`?
+ /** Is `subtp` a subtype of `tp` or of some generalization of `tp`?
+ * The generalizations of a type T are the smallest set G such that
+ *
+ * - T is in G
+ * - If a typeref R in G represents a trait, R's superclass is in G.
+ * - If a type proxy P is not a reference to a class, P's supertype is in G
*/
def isSubTypeOfParent(subtp: Type, tp: Type)(implicit ctx: Context): Boolean =
if (subtp <:< tp) true
else tp match {
- case tp: RefinedType => isSubTypeOfParent(subtp, tp.parent)
+ case tp: TypeRef if tp.symbol.isClass =>
+ tp.symbol.is(Trait) && isSubTypeOfParent(subtp, tp.firstParent)
+ case tp: TypeProxy => isSubTypeOfParent(subtp, tp.superType)
case _ => false
}
@@ -754,13 +760,11 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
case mt: MethodType if mt.paramTypes.length == 1 =>
val unapplyArgType = mt.paramTypes.head
unapp.println(i"unapp arg tpe = $unapplyArgType, pt = $selType")
- def wpt = widenForMatchSelector(selType) // needed?
val ownType =
if (selType <:< unapplyArgType) {
- //fullyDefinedType(unapplyArgType, "extractor argument", tree.pos)
unapp.println(i"case 1 $unapplyArgType ${ctx.typerState.constraint}")
selType
- } else if (isSubTypeOfParent(unapplyArgType, wpt)(ctx.addMode(Mode.GADTflexible))) {
+ } else if (isSubTypeOfParent(unapplyArgType, selType)(ctx.addMode(Mode.GADTflexible))) {
maximizeType(unapplyArgType) match {
case Some(tvar) =>
def msg =
@@ -786,9 +790,9 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
unapplyArgType
} else {
unapp.println("Neither sub nor super")
- unapp.println(TypeComparer.explained(implicit ctx => unapplyArgType <:< wpt))
+ unapp.println(TypeComparer.explained(implicit ctx => unapplyArgType <:< selType))
errorType(
- d"Pattern type $unapplyArgType is neither a subtype nor a supertype of selector type $wpt",
+ d"Pattern type $unapplyArgType is neither a subtype nor a supertype of selector type $selType",
tree.pos)
}
@@ -1047,31 +1051,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
* to form the method type.
* todo: use techniques like for implicits to pick candidates quickly?
*/
- def resolveOverloaded(alts: List[TermRef], pt: Type, targs: List[Type] = Nil)(implicit ctx: Context): List[TermRef] = track("resolveOverloaded") {
-
- def isDetermined(alts: List[TermRef]) = alts.isEmpty || alts.tail.isEmpty
-
- /** The shape of given tree as a type; cannot handle named arguments. */
- def typeShape(tree: untpd.Tree): Type = tree match {
- case untpd.Function(args, body) =>
- defn.FunctionOf(args map Function.const(defn.AnyType), typeShape(body))
- case _ =>
- defn.NothingType
- }
-
- /** The shape of given tree as a type; is more expensive than
- * typeShape but can can handle named arguments.
- */
- def treeShape(tree: untpd.Tree): Tree = tree match {
- case NamedArg(name, arg) =>
- val argShape = treeShape(arg)
- cpy.NamedArg(tree)(name, argShape).withType(argShape.tpe)
- case _ =>
- dummyTreeOfType(typeShape(tree))
- }
-
- def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] =
- alts filter (isApplicable(_, argTypes, resultType))
+ def resolveOverloaded(alts: List[TermRef], pt: Type)(implicit ctx: Context): List[TermRef] = track("resolveOverloaded") {
/** Is `alt` a method or polytype whose result type after the first value parameter
* section conforms to the expected type `resultType`? If `resultType`
@@ -1100,23 +1080,63 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
* probability of pruning the search. result type comparisons are neither cheap nor
* do they prune much, on average.
*/
- def adaptByResult(alts: List[TermRef], chosen: TermRef) = {
- def nestedCtx = ctx.fresh.setExploreTyperState
- pt match {
- case pt: FunProto if !resultConforms(chosen, pt.resultType)(nestedCtx) =>
- alts.filter(alt =>
- (alt ne chosen) && resultConforms(alt, pt.resultType)(nestedCtx)) match {
- case Nil => chosen
- case alt2 :: Nil => alt2
- case alts2 =>
- resolveOverloaded(alts2, pt) match {
- case alt2 :: Nil => alt2
- case _ => chosen
- }
- }
- case _ => chosen
- }
+ def adaptByResult(chosen: TermRef) = {
+ def nestedCtx = ctx.fresh.setExploreTyperState
+ pt match {
+ case pt: FunProto if !resultConforms(chosen, pt.resultType)(nestedCtx) =>
+ alts.filter(alt =>
+ (alt ne chosen) && resultConforms(alt, pt.resultType)(nestedCtx)) match {
+ case Nil => chosen
+ case alt2 :: Nil => alt2
+ case alts2 =>
+ resolveOverloaded(alts2, pt) match {
+ case alt2 :: Nil => alt2
+ case _ => chosen
+ }
+ }
+ case _ => chosen
}
+ }
+
+ var found = resolveOverloaded(alts, pt, Nil)(ctx.retractMode(Mode.ImplicitsEnabled))
+ if (found.isEmpty && ctx.mode.is(Mode.ImplicitsEnabled))
+ found = resolveOverloaded(alts, pt, Nil)
+ found match {
+ case alt :: Nil => adaptByResult(alt) :: Nil
+ case _ => found
+ }
+ }
+
+ /** This private version of `resolveOverloaded` does the bulk of the work of
+ * overloading resolution, but does not do result adaptation. It might be
+ * called twice from the public `resolveOverloaded` method, once with
+ * implicits enabled, and once without.
+ */
+ private def resolveOverloaded(alts: List[TermRef], pt: Type, targs: List[Type])(implicit ctx: Context): List[TermRef] = track("resolveOverloaded") {
+
+ def isDetermined(alts: List[TermRef]) = alts.isEmpty || alts.tail.isEmpty
+
+ /** The shape of given tree as a type; cannot handle named arguments. */
+ def typeShape(tree: untpd.Tree): Type = tree match {
+ case untpd.Function(args, body) =>
+ defn.FunctionOf(args map Function.const(defn.AnyType), typeShape(body))
+ case _ =>
+ defn.NothingType
+ }
+
+ /** The shape of given tree as a type; is more expensive than
+ * typeShape but can can handle named arguments.
+ */
+ def treeShape(tree: untpd.Tree): Tree = tree match {
+ case NamedArg(name, arg) =>
+ val argShape = treeShape(arg)
+ cpy.NamedArg(tree)(name, argShape).withType(argShape.tpe)
+ case _ =>
+ dummyTreeOfType(typeShape(tree))
+ }
+
+ def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] =
+ alts filter (isApplicable(_, argTypes, resultType))
val candidates = pt match {
case pt @ FunProto(args, resultType, _) =>
@@ -1176,9 +1196,10 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
}
}
- case pt @ PolyProto(targs, pt1) =>
+ case pt @ PolyProto(targs1, pt1) =>
+ assert(targs.isEmpty)
val alts1 = alts filter pt.isMatchedBy
- resolveOverloaded(alts1, pt1, targs)
+ resolveOverloaded(alts1, pt1, targs1)
case defn.FunctionOf(args, resultType) =>
narrowByTypes(alts, args, resultType)
@@ -1186,23 +1207,16 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
case pt =>
alts filter (normalizedCompatible(_, pt))
}
- narrowMostSpecific(candidates) match {
- case Nil => Nil
- case alt :: Nil =>
- adaptByResult(alts, alt) :: Nil
- // why `alts` and not `candidates`? pos/array-overload.scala gives a test case.
- // Here, only the Int-apply is a candidate, but it is not compatible with the result
- // type. Picking the Byte-apply as the only result-compatible solution then forces
- // the arguments (which are constants) to be adapted to Byte. If we had picked
- // `candidates` instead, no solution would have been found.
- case alts =>
- val noDefaults = alts.filter(!_.symbol.hasDefaultParams)
- if (noDefaults.length == 1) noDefaults // return unique alternative without default parameters if it exists
- else {
- val deepPt = pt.deepenProto
- if (deepPt ne pt) resolveOverloaded(alts, deepPt, targs)
- else alts
- }
+ val found = narrowMostSpecific(candidates)
+ if (found.length <= 1) found
+ else {
+ val noDefaults = alts.filter(!_.symbol.hasDefaultParams)
+ if (noDefaults.length == 1) noDefaults // return unique alternative without default parameters if it exists
+ else {
+ val deepPt = pt.deepenProto
+ if (deepPt ne pt) resolveOverloaded(alts, deepPt, targs)
+ else alts
+ }
}
}
@@ -1305,11 +1319,3 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
harmonizeWith(tpes)(identity, (tp, pt) => pt)
}
-/*
- def typedApply(app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[Tree], resultType: Type)(implicit ctx: Context): Tree = track("typedApply") {
- new ApplyToTyped(app, fun, methRef, args, resultType).result
- }
-
- def typedApply(fun: Tree, methRef: TermRef, args: List[Tree], resultType: Type)(implicit ctx: Context): Tree =
- typedApply(untpd.Apply(untpd.TypedSplice(fun), args), fun, methRef, args, resultType)
-*/
diff --git a/tests/neg/i1212.scala b/tests/neg/i1212.scala
new file mode 100644
index 000000000..b009a4d2c
--- /dev/null
+++ b/tests/neg/i1212.scala
@@ -0,0 +1 @@
+@ann class ann extends scala.annotation.Annotation // error: cyclic reference
diff --git a/tests/neg/patmat.scala b/tests/neg/patmat.scala
new file mode 100644
index 000000000..1e72c104a
--- /dev/null
+++ b/tests/neg/patmat.scala
@@ -0,0 +1,24 @@
+trait A
+trait B
+class C extends A with B
+case class D()
+object X {
+ def unapply(x: B): Boolean = false
+}
+
+object Test {
+ def main(args: Array[String]) = {
+ val ca: A = new C
+ ca match {
+ case x: B =>
+ case X() =>
+ case D() => // ok, but scalac disagrees
+ }
+ val cc = new C
+ cc match {
+ case x: B =>
+ case X() =>
+ case D() => // error: neither a subtype not a supertype
+ }
+ }
+}
diff --git a/tests/pos/hkgadt.scala b/tests/pos/hkgadt.scala
new file mode 100644
index 000000000..ac8caa6f3
--- /dev/null
+++ b/tests/pos/hkgadt.scala
@@ -0,0 +1,9 @@
+object HKGADT {
+ sealed trait Foo[F[_]]
+ final case class Bar() extends Foo[List]
+
+ def frob[F[_]](foo: Foo[F]) =
+ foo match {
+ case Bar() => ()
+ }
+}
diff --git a/tests/pos/i618.scala b/tests/pos/i618.scala
new file mode 100644
index 000000000..70be56cc2
--- /dev/null
+++ b/tests/pos/i618.scala
@@ -0,0 +1,3 @@
+class C(val f: Any*)
+
+class D(override val f: Nothing) extends C(f)
diff --git a/tests/neg/t2660.scala b/tests/pos/t2660.scala
index 17fe26258..695db67b9 100644
--- a/tests/neg/t2660.scala
+++ b/tests/pos/t2660.scala
@@ -1,5 +1,3 @@
-// Dotty deviation. The calls here now are classified as ambiguous.
-
package hoho
class G
@@ -22,9 +20,7 @@ class A[T](x: T) {
object T {
def main(args: Array[String]): Unit = {
implicit def g2h(g: G): H = new H
- new A[Int](new H, 23) // error
- // in the context here, either secondary constructor is applicable
- // to the other, due to the implicit in scope. So the call is ambiguous.
+ new A[Int](new H, 23)
}
}
@@ -40,7 +36,7 @@ object X {
object T2 {
def main(args: Array[String]): Unit = {
implicit def g2h(g: G): H = new H
- X.f(new H, 23) // error
+ X.f(new H, 23)
}
}
diff --git a/tests/run/i1386.scala b/tests/run/i1386.scala
new file mode 100644
index 000000000..e5f4332d2
--- /dev/null
+++ b/tests/run/i1386.scala
@@ -0,0 +1,4 @@
+object Test {
+ def main(args: Array[String]) =
+ assert(new java.util.HashMap[Int, Int]().size == 0)
+}
diff --git a/tests/run/t1381.check b/tests/run/t1381.check
new file mode 100644
index 000000000..84aec1df2
--- /dev/null
+++ b/tests/run/t1381.check
@@ -0,0 +1,7 @@
+4
+3
+2
+A
+B
+frA
+frB
diff --git a/tests/run/t1381.scala b/tests/run/t1381.scala
new file mode 100644
index 000000000..c7f49c6c3
--- /dev/null
+++ b/tests/run/t1381.scala
@@ -0,0 +1,59 @@
+object Test {
+ def main(args: Array[String]): Unit = {
+ Test1.test()
+ Test2.test()
+ Test3.test()
+ }
+}
+
+object Test1 {
+ class Bar[T](n: Int) {
+ println(n)
+ }
+ implicit def const[T](x: T): Bar[T] = new Bar[T](1)
+
+ def bar[T](e: T): Any = new Bar[T](2)
+ def bar[T](e: Bar[T]): Any = new Bar[T](3)
+
+ val b: Bar[Int] = new Bar[Int](4)
+
+ def test(): Unit = {
+ bar(b)
+ bar(5)
+ }
+}
+
+object Test2 {
+ trait A; trait B
+ class C1 {
+ def f(x: A): Unit = println("A")
+ }
+ class C2 extends C1 {
+ def f(x: B): Unit = println("B")
+ }
+ object Test extends C2 with App {
+ implicit def a2b(x: A): B = new B {}
+ def test(): Unit = {
+ f(new A {})
+ f(new B {})
+ }
+ }
+ def test(): Unit = Test.test()
+}
+
+object Test3 {
+ trait A; trait B
+ class C extends A with B
+ def fr(x: A): A = {
+ println("frA")
+ x
+ }
+ def fr(x: B): B = {
+ println("frB")
+ x
+ }
+ def test(): Unit = {
+ val a: A = fr(new C)
+ val b: B = fr(new C)
+ }
+}