diff options
-rw-r--r-- | src/dotty/tools/dotc/ast/tpd.scala | 3 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/Denotations.scala | 42 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/Applications.scala | 158 | ||||
-rw-r--r-- | tests/neg/i1212.scala | 1 | ||||
-rw-r--r-- | tests/neg/patmat.scala | 24 | ||||
-rw-r--r-- | tests/pos/hkgadt.scala | 9 | ||||
-rw-r--r-- | tests/pos/i618.scala | 3 | ||||
-rw-r--r-- | tests/pos/t2660.scala (renamed from tests/neg/t2660.scala) | 8 | ||||
-rw-r--r-- | tests/run/i1386.scala | 4 | ||||
-rw-r--r-- | tests/run/t1381.check | 7 | ||||
-rw-r--r-- | tests/run/t1381.scala | 59 |
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) + } +} |