From 27ab2152105471ac0dae2b4e1208141bb34c0e03 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Thu, 13 Oct 2016 16:43:18 +0200 Subject: [PATCH 1/2] Type#isRef: Do not strip refinements by default Stripping refinements makes sense when checking if a type is a reference to a parameterized type like Option, but this shouldn't be the default, otherwise refinements to Any may be discarded by methods like lub and glb as illustrated in the tests. --- .../tools/dotc/ast/CheckTrees.scala.disabled | 2 +- src/dotty/tools/dotc/core/Definitions.scala | 6 ++--- src/dotty/tools/dotc/core/TypeErasure.scala | 2 +- src/dotty/tools/dotc/core/Types.scala | 22 ++++++++++--------- .../tools/dotc/core/tasty/TreeUnpickler.scala | 2 +- .../core/unpickleScala2/Scala2Unpickler.scala | 2 +- .../tools/dotc/transform/ExpandSAMs.scala | 2 +- src/dotty/tools/dotc/typer/Implicits.scala | 2 +- tests/neg/isRef.scala | 7 ++++++ tests/pos/isRef.scala | 6 +++++ 10 files changed, 34 insertions(+), 19 deletions(-) create mode 100644 tests/neg/isRef.scala create mode 100644 tests/pos/isRef.scala diff --git a/src/dotty/tools/dotc/ast/CheckTrees.scala.disabled b/src/dotty/tools/dotc/ast/CheckTrees.scala.disabled index 255619f35d18..bb2cc8eee11e 100644 --- a/src/dotty/tools/dotc/ast/CheckTrees.scala.disabled +++ b/src/dotty/tools/dotc/ast/CheckTrees.scala.disabled @@ -211,7 +211,7 @@ object CheckTrees { if (rtp isRef defn.BooleanClass) check(args.isEmpty) else { - check(rtp isRef defn.OptionClass) + check(rtp.isRef(defn.OptionClass, stripRefinements = true)) val normArgs = rtp.argTypesHi match { case optionArg :: Nil => optionArg.argTypesHi match { diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 50746c61dc03..3d189af1033b 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -564,7 +564,7 @@ class Definitions { if (ctx.erasedTypes) JavaArrayType(elem) else ArrayType.appliedTo(elem :: Nil) def unapply(tp: Type)(implicit ctx: Context): Option[Type] = tp.dealias match { - case at: RefinedType if (at isRef ArrayType.symbol) && at.argInfos.length == 1 => Some(at.argInfos.head) + case at: RefinedType if (at.parent.isRef(ArrayType.symbol)) && at.argInfos.length == 1 => Some(at.argInfos.head) case _ => None } } @@ -667,7 +667,7 @@ class Definitions { def isTupleType(tp: Type)(implicit ctx: Context) = { val arity = tp.dealias.argInfos.length - arity <= MaxTupleArity && TupleType(arity) != null && (tp isRef TupleType(arity).symbol) + arity <= MaxTupleArity && TupleType(arity) != null && (tp.isRef(TupleType(arity).symbol, stripRefinements = true)) } def tupleType(elems: List[Type]) = { @@ -679,7 +679,7 @@ class Definitions { def isFunctionType(tp: Type)(implicit ctx: Context) = { val arity = functionArity(tp) - 0 <= arity && arity <= MaxFunctionArity && (tp isRef FunctionType(arity).symbol) + 0 <= arity && arity <= MaxFunctionArity && (tp.isRef(FunctionType(arity).symbol, stripRefinements = true)) } def functionArity(tp: Type)(implicit ctx: Context) = tp.dealias.argInfos.length - 1 diff --git a/src/dotty/tools/dotc/core/TypeErasure.scala b/src/dotty/tools/dotc/core/TypeErasure.scala index fd5fcb921b85..e65008545ebf 100644 --- a/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/src/dotty/tools/dotc/core/TypeErasure.scala @@ -446,7 +446,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean // constructor method should not be semi-erased. else if (isConstructor && isDerivedValueClass(sym)) eraseNormalClassRef(tp) else this(tp) - case RefinedType(parent, _, _) if !(parent isRef defn.ArrayClass) => + case RefinedType(parent, _, _) if !(parent.isRef(defn.ArrayClass, stripRefinements = true)) => eraseResult(parent) case _ => this(tp) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index d242843e55d4..4d25f2a3f525 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -106,20 +106,22 @@ object Types { case _ => false } - /** Is this type a (possibly refined or applied or aliased) type reference - * to the given type symbol? - * @sym The symbol to compare to. It must be a class symbol or abstract type. - * It makes no sense for it to be an alias type because isRef would always - * return false in that case. + /** Is this type a (possibly aliased) type reference to the given type symbol? + * + * @param sym The symbol to compare to. It must be a class symbol or abstract type. + * It makes no sense for it to be an alias type because isRef would always + * return false in that case. + * @param stripRefinements If true, go through refinements. */ - def isRef(sym: Symbol)(implicit ctx: Context): Boolean = stripAnnots.stripTypeVar match { + def isRef(sym: Symbol, stripRefinements: Boolean = false)(implicit ctx: Context): Boolean = stripAnnots.stripTypeVar match { case this1: TypeRef => this1.info match { // see comment in Namer#typeDefSig - case TypeAlias(tp) => tp.isRef(sym) + case TypeAlias(tp) => tp.isRef(sym, stripRefinements) case _ => this1.symbol eq sym } - case this1: RefinedOrRecType => this1.parent.isRef(sym) - case this1: HKApply => this1.superType.isRef(sym) + case this1: RefinedType if stripRefinements => this1.parent.isRef(sym, stripRefinements) + case this1: RecType => this1.parent.isRef(sym, stripRefinements) + case this1: HKApply => this1.superType.isRef(sym, stripRefinements) case _ => false } @@ -3313,7 +3315,7 @@ object Types { case mt: MethodType if !mt.isDependent => Some(absMems.head) case _ => None } - else if (tp isRef defn.PartialFunctionClass) + else if (tp.isRef(defn.PartialFunctionClass, stripRefinements = true)) // To maintain compatibility with 2.x, we treat PartialFunction specially, // pretending it is a SAM type. In the future it would be better to merge // Function and PartialFunction, have Function1 contain a isDefinedAt method diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index f67159808e0a..d2ea62366fac 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -911,7 +911,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table, posUnpickle val expr = readTerm() val tpt = readTpt() val expr1 = expr match { - case SeqLiteral(elems, elemtpt) if tpt.tpe.isRef(defn.ArrayClass) => + case SeqLiteral(elems, elemtpt) if tpt.tpe.isRef(defn.ArrayClass, stripRefinements = true) => JavaSeqLiteral(elems, elemtpt) case expr => expr } diff --git a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 70148b3e2a61..99a6946dc4f3 100644 --- a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -71,7 +71,7 @@ object Scala2Unpickler { def arrayToRepeated(tp: Type)(implicit ctx: Context): Type = tp match { case tp @ MethodType(paramNames, paramTypes) => val lastArg = paramTypes.last - assert(lastArg isRef defn.ArrayClass) + assert(lastArg.isRef(defn.ArrayClass, stripRefinements = true)) val elemtp0 :: Nil = lastArg.baseArgInfos(defn.ArrayClass) val elemtp = elemtp0 match { case AndType(t1, t2) if t1.typeSymbol.isAbstractType && (t2 isRef defn.ObjectClass) => diff --git a/src/dotty/tools/dotc/transform/ExpandSAMs.scala b/src/dotty/tools/dotc/transform/ExpandSAMs.scala index 91399f91a15b..883d2d83c082 100644 --- a/src/dotty/tools/dotc/transform/ExpandSAMs.scala +++ b/src/dotty/tools/dotc/transform/ExpandSAMs.scala @@ -33,7 +33,7 @@ class ExpandSAMs extends MiniPhaseTransform { thisTransformer => case Block(stats @ (fn: DefDef) :: Nil, Closure(_, fnRef, tpt)) if fnRef.symbol == fn.symbol => tpt.tpe match { case NoType => tree // it's a plain function - case tpe @ SAMType(_) if tpe.isRef(defn.PartialFunctionClass) => + case tpe @ SAMType(_) if tpe.isRef(defn.PartialFunctionClass, stripRefinements = true) => toPartialFunction(tree) case tpe @ SAMType(_) if isPlatformSam(tpe.classSymbol.asClass) => tree diff --git a/src/dotty/tools/dotc/typer/Implicits.scala b/src/dotty/tools/dotc/typer/Implicits.scala index d6cf7fb2bdd9..3d291d894004 100644 --- a/src/dotty/tools/dotc/typer/Implicits.scala +++ b/src/dotty/tools/dotc/typer/Implicits.scala @@ -478,7 +478,7 @@ trait Implicits { self: Typer => * synthesize a class tag for `T`. */ def synthesizedClassTag(formal: Type, pos: Position)(implicit ctx: Context): Tree = { - if (formal.isRef(defn.ClassTagClass)) + if (formal.isRef(defn.ClassTagClass, stripRefinements = true)) formal.argTypes match { case arg :: Nil => val tp = fullyDefinedType(arg, "ClassTag argument", pos) diff --git a/tests/neg/isRef.scala b/tests/neg/isRef.scala new file mode 100644 index 000000000000..e780725e2aa1 --- /dev/null +++ b/tests/neg/isRef.scala @@ -0,0 +1,7 @@ +trait Foo { + type A = (Any { type T = Int }) + type B = (Any { type S = String }) + def a: A + def b: B + def aandb: A & B = b // error: found: B, required: A & B +} diff --git a/tests/pos/isRef.scala b/tests/pos/isRef.scala new file mode 100644 index 000000000000..ee9cf6108cbe --- /dev/null +++ b/tests/pos/isRef.scala @@ -0,0 +1,6 @@ +trait Foo { + type A = (Any { type T = Int }) + type B = (Any { type S = String }) + def b: B + def aorb: A | B = b +} From 30b6d8bc6ed40500def43b31d8449b5094267456 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Thu, 13 Oct 2016 16:53:52 +0200 Subject: [PATCH 2/2] Type#isRef: Consider both bounds for HKApply Otherwise any applied higher-kinded type upper-bounded by Any is considered to be a reference to Any and will be mishandled by methods like lub and glb, see the tests. --- src/dotty/tools/dotc/core/Types.scala | 2 +- tests/neg/isRef.scala | 7 +++++++ tests/pos/isRef.scala | 7 +++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 4d25f2a3f525..041a7ec2f2d2 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -121,7 +121,7 @@ object Types { } case this1: RefinedType if stripRefinements => this1.parent.isRef(sym, stripRefinements) case this1: RecType => this1.parent.isRef(sym, stripRefinements) - case this1: HKApply => this1.superType.isRef(sym, stripRefinements) + case this1: HKApply => this1.superType.isRef(sym, stripRefinements) && this1.lowerBound.isRef(sym, stripRefinements) case _ => false } diff --git a/tests/neg/isRef.scala b/tests/neg/isRef.scala index e780725e2aa1..e389e87aa055 100644 --- a/tests/neg/isRef.scala +++ b/tests/neg/isRef.scala @@ -5,3 +5,10 @@ trait Foo { def b: B def aandb: A & B = b // error: found: B, required: A & B } + +trait Foo2 { + type A[_] + type B[_] + def b: B[Int] + def aandb: A[Int] & B[Int] = b // error: found: B[Int], required: A[Int] & B[Int] +} diff --git a/tests/pos/isRef.scala b/tests/pos/isRef.scala index ee9cf6108cbe..cbabf66c7df9 100644 --- a/tests/pos/isRef.scala +++ b/tests/pos/isRef.scala @@ -4,3 +4,10 @@ trait Foo { def b: B def aorb: A | B = b } + +trait Foo2 { + type A[_] + type B[_] + def b: B[Int] + def aorb: A[Int] | B[Int] = b +}