Skip to content

Commit 959ea0c

Browse files
authored
Merge pull request scala#1461 from dotty-staging/fixes-gadts
Fixes of GADTs and test recategorization.
2 parents fb19d0b + e61ff6f commit 959ea0c

File tree

109 files changed

+155
-102
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+155
-102
lines changed

src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._
88
import Decorators._
99
import language.higherKinds
1010
import collection.mutable.ListBuffer
11+
import util.Attachment
1112
import config.Printers._
1213

1314
object desugar {
@@ -17,6 +18,11 @@ object desugar {
1718

1819
import untpd._
1920

21+
/** Tags a .withFilter call generated by desugaring a for expression.
22+
* Such calls can alternatively be rewritten to use filter.
23+
*/
24+
val MaybeFilter = new Attachment.Key[Unit]
25+
2026
/** Info of a variable in a pattern: The named tree and its type */
2127
private type VarInfo = (NameTree, Tree)
2228

@@ -773,6 +779,12 @@ object desugar {
773779
(Bind(name, pat), Ident(name))
774780
}
775781

782+
/** Add MaybeFilter attachment */
783+
def orFilter(tree: Tree): tree.type = {
784+
tree.putAttachment(MaybeFilter, ())
785+
tree
786+
}
787+
776788
/** Make a pattern filter:
777789
* rhs.withFilter { case pat => true case _ => false }
778790
*
@@ -803,7 +815,7 @@ object desugar {
803815
val cases = List(
804816
CaseDef(pat, EmptyTree, Literal(Constant(true))),
805817
CaseDef(Ident(nme.WILDCARD), EmptyTree, Literal(Constant(false))))
806-
Apply(Select(rhs, nme.withFilter), makeCaseLambda(cases))
818+
Apply(orFilter(Select(rhs, nme.withFilter)), makeCaseLambda(cases))
807819
}
808820

809821
/** Is pattern `pat` irrefutable when matched against `rhs`?
@@ -858,7 +870,7 @@ object desugar {
858870
val vfrom1 = new IrrefutableGenFrom(makeTuple(allpats), rhs1)
859871
makeFor(mapName, flatMapName, vfrom1 :: rest1, body)
860872
case (gen: GenFrom) :: test :: rest =>
861-
val filtered = Apply(rhsSelect(gen, nme.withFilter), makeLambda(gen.pat, test))
873+
val filtered = Apply(orFilter(rhsSelect(gen, nme.withFilter)), makeLambda(gen.pat, test))
862874
val genFrom =
863875
if (isIrrefutableGenFrom(gen)) new IrrefutableGenFrom(gen.pat, filtered)
864876
else GenFrom(gen.pat, filtered)

src/dotty/tools/dotc/core/TypeComparer.scala

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,19 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
7676
myNothingType
7777
}
7878

79+
/** Indicates whether a previous subtype check used GADT bounds */
80+
var GADTused = false
81+
82+
/** Record that GADT bounds of `sym` were used in a subtype check.
83+
* But exclude constructor type parameters, as these are aliased
84+
* to the corresponding class parameters, which does not constitute
85+
* a true usage of a GADT symbol.
86+
*/
87+
private def GADTusage(sym: Symbol) = {
88+
if (!sym.owner.isConstructor) GADTused = true
89+
true
90+
}
91+
7992
// Subtype testing `<:<`
8093

8194
def topLevelSubType(tp1: Type, tp2: Type): Boolean = {
@@ -325,7 +338,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
325338
val gbounds2 = ctx.gadt.bounds(tp2.symbol)
326339
(gbounds2 != null) &&
327340
(isSubTypeWhenFrozen(tp1, gbounds2.lo) ||
328-
narrowGADTBounds(tp2, tp1, isUpper = false))
341+
narrowGADTBounds(tp2, tp1, isUpper = false)) &&
342+
GADTusage(tp2.symbol)
329343
}
330344
((frozenConstraint || !isCappable(tp1)) && isSubType(tp1, lo2) ||
331345
compareGADT ||
@@ -507,7 +521,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
507521
val gbounds1 = ctx.gadt.bounds(tp1.symbol)
508522
(gbounds1 != null) &&
509523
(isSubTypeWhenFrozen(gbounds1.hi, tp2) ||
510-
narrowGADTBounds(tp1, tp2, isUpper = true))
524+
narrowGADTBounds(tp1, tp2, isUpper = true)) &&
525+
GADTusage(tp1.symbol)
511526
}
512527
isSubType(hi1, tp2) || compareGADT
513528
case _ =>
@@ -846,11 +861,13 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
846861
// special case for situations like:
847862
// class C { type T }
848863
// val foo: C
849-
// foo.type <: C { type T = foo.T }
864+
// foo.type <: C { type T {= , <: , >:} foo.T }
850865
def selfReferentialMatch = tp1.isInstanceOf[SingletonType] && {
851866
rinfo2 match {
852-
case rinfo2: TypeAlias =>
853-
!defn.isBottomType(tp1.widen) && (tp1 select name) =:= rinfo2.alias
867+
case rinfo2: TypeBounds =>
868+
val mbr1 = tp1.select(name)
869+
!defn.isBottomType(tp1.widen) &&
870+
(mbr1 =:= rinfo2.hi || (rinfo2.hi ne rinfo2.lo) && mbr1 =:= rinfo2.lo)
854871
case _ => false
855872
}
856873
}

src/dotty/tools/dotc/transform/TreeChecker.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ class TreeChecker extends Phase with SymTransformer {
258258
}
259259

260260
override def typed(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): tpd.Tree = {
261-
val tpdTree = super.typed(tree)
261+
val tpdTree = super.typed(tree, pt)
262262
checkIdentNotJavaClass(tpdTree)
263263
tpdTree
264264
}

src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -775,14 +775,13 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
775775
* The generalizations of a type T are the smallest set G such that
776776
*
777777
* - T is in G
778-
* - If a typeref R in G represents a trait, R's superclass is in G.
778+
* - If a typeref R in G represents a class or trait, R's superclass is in G.
779779
* - If a type proxy P is not a reference to a class, P's supertype is in G
780780
*/
781781
def isSubTypeOfParent(subtp: Type, tp: Type)(implicit ctx: Context): Boolean =
782782
if (subtp <:< tp) true
783783
else tp match {
784-
case tp: TypeRef if tp.symbol.isClass =>
785-
tp.symbol.is(Trait) && isSubTypeOfParent(subtp, tp.firstParent)
784+
case tp: TypeRef if tp.symbol.isClass => isSubTypeOfParent(subtp, tp.firstParent)
786785
case tp: TypeProxy => isSubTypeOfParent(subtp, tp.superType)
787786
case _ => false
788787
}

src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,7 @@ class Namer { typer: Typer =>
726726
// the parent types are elaborated.
727727
index(constr)
728728
symbolOfTree(constr).ensureCompleted()
729-
729+
730730
index(rest)(inClassContext(selfInfo))
731731

732732
val tparamAccessors = decls.filter(_ is TypeParamAccessor).toList
@@ -807,20 +807,27 @@ class Namer { typer: Typer =>
807807
lazy val schema = paramFn(WildcardType)
808808
val site = sym.owner.thisType
809809
((NoType: Type) /: sym.owner.info.baseClasses.tail) { (tp, cls) =>
810-
val iRawInfo =
811-
cls.info.nonPrivateDecl(sym.name).matchingDenotation(site, schema).info
812-
val iInstInfo = iRawInfo match {
813-
case iRawInfo: PolyType =>
814-
if (iRawInfo.paramNames.length == typeParams.length)
815-
iRawInfo.instantiate(typeParams map (_.typeRef))
810+
def instantiatedResType(info: Type, tparams: List[Symbol], paramss: List[List[Symbol]]): Type = info match {
811+
case info: PolyType =>
812+
if (info.paramNames.length == typeParams.length)
813+
instantiatedResType(info.instantiate(tparams.map(_.typeRef)), Nil, paramss)
816814
else NoType
815+
case info: MethodType =>
816+
paramss match {
817+
case params :: paramss1 if info.paramNames.length == params.length =>
818+
instantiatedResType(info.instantiate(params.map(_.termRef)), tparams, paramss1)
819+
case _ =>
820+
NoType
821+
}
817822
case _ =>
818-
if (typeParams.isEmpty) iRawInfo
823+
if (tparams.isEmpty && paramss.isEmpty) info.widenExpr
819824
else NoType
820825
}
821-
val iResType = iInstInfo.finalResultType.asSeenFrom(site, cls)
826+
val iRawInfo =
827+
cls.info.nonPrivateDecl(sym.name).matchingDenotation(site, schema).info
828+
val iResType = instantiatedResType(iRawInfo, typeParams, paramss).asSeenFrom(site, cls)
822829
if (iResType.exists)
823-
typr.println(i"using inherited type for ${mdef.name}; raw: $iRawInfo, inst: $iInstInfo, inherited: $iResType")
830+
typr.println(i"using inherited type for ${mdef.name}; raw: $iRawInfo, inherited: $iResType")
824831
tp & iResType
825832
}
826833
}

src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -346,11 +346,17 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
346346
}
347347
}
348348

349-
if (ctx.compilationUnit.isJava && tree.name.isTypeName) {
349+
def selectWithFallback(fallBack: => Tree) =
350+
tryEither(tryCtx => asSelect(tryCtx))((_, _) => fallBack)
351+
352+
if (ctx.compilationUnit.isJava && tree.name.isTypeName)
350353
// SI-3120 Java uses the same syntax, A.B, to express selection from the
351354
// value A and from the type A. We have to try both.
352-
tryEither(tryCtx => asSelect(tryCtx))((_, _) => asJavaSelectFromTypeTree(ctx))
353-
} else asSelect(ctx)
355+
selectWithFallback(asJavaSelectFromTypeTree(ctx))
356+
else if (tree.name == nme.withFilter && tree.getAttachment(desugar.MaybeFilter).isDefined)
357+
selectWithFallback(typedSelect(untpd.cpy.Select(tree)(tree.qualifier, nme.filter), pt))
358+
else
359+
asSelect(ctx)
354360
}
355361

356362
def typedSelectFromTypeTree(tree: untpd.SelectFromTypeTree, pt: Type)(implicit ctx: Context): Tree = track("typedSelectFromTypeTree") {
@@ -1066,8 +1072,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
10661072
def completeAnnotations(mdef: untpd.MemberDef, sym: Symbol)(implicit ctx: Context): Unit = {
10671073
// necessary to force annotation trees to be computed.
10681074
sym.annotations.foreach(_.tree)
1075+
val annotCtx = ctx.outersIterator.dropWhile(_.owner == sym).next
10691076
// necessary in order to mark the typed ahead annotations as definitely typed:
1070-
untpd.modsDeco(mdef).mods.annotations.foreach(typedAnnotation)
1077+
untpd.modsDeco(mdef).mods.annotations.foreach(typedAnnotation(_)(annotCtx))
10711078
}
10721079

10731080
def typedAnnotation(annot: untpd.Tree)(implicit ctx: Context): Tree = track("typedAnnotation") {
@@ -1715,6 +1722,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
17151722
else
17161723
missingArgs
17171724
case _ =>
1725+
ctx.typeComparer.GADTused = false
17181726
if (ctx.mode is Mode.Pattern) {
17191727
tree match {
17201728
case _: RefTree | _: Literal if !isVarPattern(tree) =>
@@ -1723,7 +1731,15 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
17231731
}
17241732
tree
17251733
}
1726-
else if (tree.tpe <:< pt) tree
1734+
else if (tree.tpe <:< pt)
1735+
if (ctx.typeComparer.GADTused && pt.isValueType)
1736+
// Insert an explicit cast, so that -Ycheck in later phases succeeds.
1737+
// I suspect, but am not 100% sure that this might affect inferred types,
1738+
// if the expected type is a supertype of the GADT bound. It would be good to come
1739+
// up with a test case for this.
1740+
tree.asInstance(pt)
1741+
else
1742+
tree
17271743
else if (wtp.isInstanceOf[MethodType]) missingArgs
17281744
else {
17291745
typr.println(i"adapt to subtype ${tree.tpe} !<:< $pt")

test/dotc/tests.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ class tests extends CompilerTest {
110110
@Test def rewrites = compileFile(posScala2Dir, "rewrites", "-rewrite" :: scala2mode)
111111

112112
@Test def pos_859 = compileFile(posSpecialDir, "i859", scala2mode)(allowDeepSubtypes)
113+
@Test def pos_t8146a = compileFile(posSpecialDir, "t8146a")(allowDeepSubtypes)
113114

114115
@Test def pos_t5545 = {
115116
// compile by hand in two batches, since junit lacks the infrastructure to

tests/run/t5544/Api_1.scala renamed to tests/disabled/not-representable/pos/t5544/Api_1.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Uses structural types; therefore not expressible in dotty
12
import scala.annotation.StaticAnnotation
23

34
class ann(val bar: Any) extends StaticAnnotation

tests/pending/pos/t7035.scala renamed to tests/disabled/not-representable/pos/t7035.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// no longer works because dotty uses name-nased pattern matching for case classes
2+
13
case class Y(final var x: Int, final private var y: String, final val z1: Boolean, final private val z2: Any) {
24

35
import Test.{y => someY}

tests/pending/pos/t7228.scala renamed to tests/disabled/not-representable/pos/t7228.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// no longer works because dotty does not have a concept of weak conformance
12
object AdaptWithWeaklyConformantType {
23
implicit class D(d: Double) { def double = d*2 }
34

tests/pending/pos/t8111.scala renamed to tests/disabled/not-representable/pos/t8111.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// structural types, cannot represent
12
trait T {
23

34
def crashy(ma: Any): Unit = {

tests/pending/pos/t7239.scala renamed to tests/neg/t7239.scala

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// Dotty rewrites only withFilter calls occurring in for expressions to filter calls.
2+
// So this test does not compile.
13
object Test {
24
def BrokenMethod(): HasFilter[(Int, String)] = ???
35

@@ -15,12 +17,12 @@ object Test {
1517
(implicit F0: NoImplicit): HasWithFilter = ???
1618
}
1719

18-
BrokenMethod().withFilter(_ => true) // okay
19-
BrokenMethod().filter(_ => true) // okay
20+
BrokenMethod().withFilter(_ => true) // error
21+
BrokenMethod().filter(_ => true) // ok
2022

2123
locally {
2224
import addWithFilter._
23-
BrokenMethod().withFilter((_: (Int, String)) => true) // okay
25+
BrokenMethod().withFilter((_: (Int, String)) => true) // error
2426
}
2527

2628
locally {
@@ -33,6 +35,6 @@ object Test {
3335
// `(B => Boolean)`. Only later during pickling does the
3436
// defensive check for erroneous types in the tree pick up
3537
// the problem.
36-
BrokenMethod().withFilter(x => true) // erroneous or inaccessible type.
38+
BrokenMethod().withFilter(x => true) // error
3739
}
3840
}

tests/pending/pos/t8002-nested-scope.scala renamed to tests/neg/t8002-nested-scope.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class C {
1313
{
1414
val a = 0
1515
object C {
16-
new C().x
16+
new C().x // error: cannot be accessed
1717
}
1818
}
1919
}

tests/pending/neg/i533/Compat.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
object Compat {
2+
def main(args: Array[String]): Unit = {
3+
val x = new Array[Int](1)
4+
x(0) = 10
5+
println(JA.get(x))
6+
}
7+
}

tests/pending/neg/i533/JA.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class JA {
2+
public static <T> T get(T[] arr) {
3+
return arr[0];
4+
}
5+
}

tests/pending/pos/contraImplicits.scala

Lines changed: 0 additions & 18 deletions
This file was deleted.

tests/pending/pos/depmet_implicit_norm_ret.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,18 @@ object Test{
1717
}
1818
}
1919

20+
import ZipWith._
21+
2022
trait ZipWith[S] {
2123
type T
2224
def zipWith : S => T = sys.error("")
2325
}
2426

2527
// bug: inferred return type = (Stream[A]) => java.lang.Object with Test.ZipWith[B]{type T = Stream[B]}#T
2628
// this seems incompatible with vvvvvvvvvvvvvvvvvvvvvv -- #3731
27-
def map[A,B](f : A => B) /* : Stream[A] => Stream[B]*/ = ZipWith(f)
28-
val tst: Stream[Int] = map{x: String => x.length}(Stream("a"))
29+
def map1[A,B](f : A => B) = ZipWith(f)(SuccZipWith) // this typechecks but fails in -Ycheck:first
30+
val tst1: Stream[Int] = map1[String, Int]{x: String => x.length}.apply(Stream("a"))
31+
32+
def map2[A,B](f : A => B) = ZipWith(f) // this finds ZeroZipWith where scalac finds SuccZipWith and fails typechecking in the next line.
33+
val tst2: Stream[Int] = map2{x: String => x.length}.apply(Stream("a"))
2934
}

tests/pending/pos/depsel.scala

Lines changed: 0 additions & 14 deletions
This file was deleted.

tests/pending/pos/exponential-spec.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ object Test {
2323
compose f[T] // 8s
2424
compose f[T] // 11s
2525
compose f[T] // 17s
26-
compose f[T] // 29s
26+
/* compose f[T] // 29s
2727
compose f[T] // 54s
2828
compose f[T]
2929
compose f[T]
@@ -42,6 +42,6 @@ object Test {
4242
compose f[T]
4343
compose f[T]
4444
compose f[T]
45-
compose f[T]
45+
compose f[T]*/
4646
)(exp)
4747
}

tests/pending/pos/generic-sigs.flags

Lines changed: 0 additions & 1 deletion
This file was deleted.

tests/pending/pos/infersingle.flags

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)