From 17a6080c262ba71e2b513f411b2e8c033ced7ce4 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Sat, 5 Dec 2020 17:37:21 +0100 Subject: [PATCH 1/3] Fix #8001: Erase polymorphic value classes like Scala 2 Previously given: class Poly[A](val value: A) extends AnyVal We always erased `Poly[X]` to `Object`, no matter the value `X`, because the erasure was the erased underlying type as seen from its definition, and not as seen from the current prefix. But it turns out that in Scala 2, `Foo[Int]` will be erased to `Integer` instead (it would have made more sense to use `int` but I suspect this is more accidental than designed). To be binary-compatible with Scala 2 and to support the same kind of overloads we need to replicate its behavior, more precisely the rules I was able to reverse-engineer are: - Given `class Foo[A](x: A) extends AnyVal`, `Foo[X]` should erase like `X`, except if its a primitive in which case it erases to the boxed version of this primitive. - Given `class Bar[A](x: Array[A]) extends AnyVal`, `Bar[X]` will be erased like `Array[A]` as seen from its definition site, no matter the `X` (same if `A` is bounded). I was able to adapt our implementation of value class erasure to these new rules without too much refactoring through one compromise: a value class can no longer wrap another value class. This was never supported by Scala 2 so we can afford to not support it either. --- .../tools/backend/jvm/BCodeHelpers.scala | 2 +- .../dotty/tools/dotc/core/TypeComparer.scala | 2 +- .../dotty/tools/dotc/core/TypeErasure.scala | 78 ++++++++++++------- .../tools/dotc/reporting/ErrorMessageID.scala | 2 +- .../dotty/tools/dotc/reporting/messages.scala | 6 +- .../dotty/tools/dotc/transform/Erasure.scala | 6 +- .../tools/dotc/transform/FirstTransform.scala | 3 +- .../dotc/transform/GenericSignatures.scala | 7 +- .../tools/dotc/transform/ValueClasses.scala | 13 +--- .../src/dotty/tools/dotc/typer/Checking.scala | 4 +- .../sbt-test/scala2-compat/i8001/build.sbt | 15 ++++ .../scala2-compat/i8001/lib/lib.scala | 43 ++++++++++ .../scala2-compat/i8001/main/test.scala | 20 +++++ .../scala2-compat/i8001/project/plugins.sbt | 1 + sbt-dotty/sbt-test/scala2-compat/i8001/test | 1 + tests/neg/i1642.scala | 5 +- tests/neg/i5005.scala | 2 +- tests/pos/i1642.scala | 2 - tests/run/i8001/A_1.scala | 71 +++++++++++++++++ tests/run/i8001/B_2.java | 27 +++++++ tests/run/i8001/Test_3.scala | 4 + tests/run/t7685-class-simple.scala | 10 --- 22 files changed, 256 insertions(+), 68 deletions(-) create mode 100644 sbt-dotty/sbt-test/scala2-compat/i8001/build.sbt create mode 100644 sbt-dotty/sbt-test/scala2-compat/i8001/lib/lib.scala create mode 100644 sbt-dotty/sbt-test/scala2-compat/i8001/main/test.scala create mode 100644 sbt-dotty/sbt-test/scala2-compat/i8001/project/plugins.sbt create mode 100644 sbt-dotty/sbt-test/scala2-compat/i8001/test delete mode 100644 tests/pos/i1642.scala create mode 100644 tests/run/i8001/A_1.scala create mode 100644 tests/run/i8001/B_2.java create mode 100644 tests/run/i8001/Test_3.scala delete mode 100644 tests/run/t7685-class-simple.scala diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala b/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala index 20d9e34f0ab0..2391574edeb2 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala @@ -918,7 +918,7 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { // (one that doesn't erase to the actual signature). See run/t3452b for a test case. val memberTpe = atPhase(erasurePhase) { moduleClass.denot.thisType.memberInfo(sym) } - val erasedMemberType = TypeErasure.erasure(memberTpe) + val erasedMemberType = TypeErasure.fullErasure(memberTpe) if (erasedMemberType =:= sym.denot.info) getGenericSignatureHelper(sym, moduleClass, memberTpe).orNull else null diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 0f935b21a5d4..4c85c684d144 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -341,7 +341,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case TypeErasure.ErasedValueType(tycon1, underlying2) => def compareErasedValueType = tp1 match { case TypeErasure.ErasedValueType(tycon2, underlying1) => - (tycon1.symbol eq tycon2.symbol) && isSameType(underlying1, underlying2) + (tycon1.symbol eq tycon2.symbol) && isSubType(underlying1, underlying2) case _ => secondTry } diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index d142ccd5f5ac..b5bd1cfb92bc 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -36,7 +36,7 @@ import scala.annotation.tailrec object TypeErasure { private def erasureDependsOnArgs(sym: Symbol)(using Context) = - sym == defn.ArrayClass || sym == defn.PairClass + sym == defn.ArrayClass || sym == defn.PairClass || isDerivedValueClass(sym) def normalizeClass(cls: ClassSymbol)(using Context): ClassSymbol = { if (cls.owner == defn.ScalaPackageClass) { @@ -59,7 +59,7 @@ object TypeErasure { case tp: TypeRef => val sym = tp.symbol sym.isClass && - !erasureDependsOnArgs(sym) && + (!erasureDependsOnArgs(sym) || isDerivedValueClass(sym)) && !defn.specialErasure.contains(sym) && !defn.isSyntheticFunctionClass(sym) case _: TermRef => @@ -157,7 +157,7 @@ object TypeErasure { def sigName(tp: Type, isJava: Boolean)(using Context): TypeName = { val normTp = tp.translateFromRepeated(toArray = isJava) - val erase = erasureFn(isJava, semiEraseVCs = false, isConstructor = false, wildcardOK = true) + val erase = erasureFn(isJava, semiEraseVCs = true, isConstructor = false, wildcardOK = true) erase.sigName(normTp)(using preErasureCtx) } @@ -444,7 +444,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean case tp: TypeRef => val sym = tp.symbol if (!sym.isClass) this(tp.translucentSuperType) - else if (semiEraseVCs && isDerivedValueClass(sym)) eraseDerivedValueClassRef(tp) + else if (semiEraseVCs && isDerivedValueClass(sym)) eraseDerivedValueClass(tp) else if (defn.isSyntheticFunctionClass(sym)) defn.erasedFunctionType(sym) else eraseNormalClassRef(tp) case tp: AppliedType => @@ -452,6 +452,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean if (tycon.isRef(defn.ArrayClass)) eraseArray(tp) else if (tycon.isRef(defn.PairClass)) erasePair(tp) else if (tp.isRepeatedParam) apply(tp.translateFromRepeated(toArray = isJava)) + else if (semiEraseVCs && isDerivedValueClass(tycon.classSymbol)) eraseDerivedValueClass(tp) else apply(tp.translucentSuperType) case _: TermRef | _: ThisType => this(tp.widen) @@ -551,12 +552,34 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean case rt => MethodType(Nil, Nil, rt) case tp1 => this(tp1) - private def eraseDerivedValueClassRef(tref: TypeRef)(using Context): Type = { - val cls = tref.symbol.asClass - val underlying = underlyingOfValueClass(cls) - if underlying.exists && !isCyclic(cls) then - val erasedValue = valueErasure(underlying) - if erasedValue.exists then ErasedValueType(tref, erasedValue) + private def eraseDerivedValueClass(tp: Type)(using Context): Type = { + val cls = tp.classSymbol.asClass + val unbox = valueClassUnbox(cls) + if unbox.exists then + val genericUnderlying = unbox.info.resultType + val underlying = tp.select(unbox).widen.resultType + + val erasedUnderlying = erasure(underlying) + + // Ideally, we would just use `erasedUnderlying` as the erasure of `tp`, but to + // be binary-compatible with Scala 2 we need two special cases for polymorphic + // value classes: + // - Given `class Foo[A](x: A) extends AnyVal`, `Foo[X]` should erase like + // `X`, except if its a primitive in which case it erases to the boxed + // version of this primitive. + // - Given `class Bar[A](x: Array[A]) extends AnyVal`, `Bar[X]` will be + // erased like `Array[A]` as seen from its definition site, no matter + // the `X` (same if `A` is bounded). + // + // The binary compatibility is checked by sbt-dotty/sbt-test/scala2-compat/i8001 + val erasedValueClass = + if erasedUnderlying.isPrimitiveValueType && !genericUnderlying.isPrimitiveValueType then + defn.boxedType(erasedUnderlying) + else if genericUnderlying.derivesFrom(defn.ArrayClass) then + erasure(genericUnderlying) + else erasedUnderlying + + if erasedValueClass.exists then ErasedValueType(cls.typeRef, erasedValueClass) else assert(ctx.reporter.errorsReported, i"no erasure for $underlying") NoType @@ -569,22 +592,23 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean } /** The erasure of a function result type. */ - private def eraseResult(tp: Type)(using Context): Type = tp match { - case tp: TypeRef => - val sym = tp.symbol - if (sym eq defn.UnitClass) sym.typeRef - // For a value class V, "new V(x)" should have type V for type adaptation to work - // correctly (see SIP-15 and [[Erasure.Boxing.adaptToType]]), so the return type of a - // constructor method should not be semi-erased. - else if (isConstructor && isDerivedValueClass(sym)) eraseNormalClassRef(tp) - else this(tp) - case tp: AppliedType => - val sym = tp.tycon.typeSymbol - if (sym.isClass && !erasureDependsOnArgs(sym)) eraseResult(tp.tycon) - else this(tp) - case _ => - this(tp) - } + def eraseResult(tp: Type)(using Context): Type = + // For a value class V, "new V(x)" should have type V for type adaptation to work + // correctly (see SIP-15 and [[Erasure.Boxing.adaptToType]]), so the result type of a + // constructor method should not be semi-erased. + if semiEraseVCs && isConstructor && !tp.isInstanceOf[MethodOrPoly] then + erasureFn(isJava, semiEraseVCs = false, isConstructor, wildcardOK).eraseResult(tp) + else tp match + case tp: TypeRef => + val sym = tp.symbol + if (sym eq defn.UnitClass) sym.typeRef + else this(tp) + case tp: AppliedType => + val sym = tp.tycon.typeSymbol + if (sym.isClass && !erasureDependsOnArgs(sym)) eraseResult(tp.tycon) + else this(tp) + case _ => + this(tp) /** The name of the type as it is used in `Signature`s. * Need to ensure correspondence with erasure! @@ -602,7 +626,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean return sigName(info) } if (isDerivedValueClass(sym)) { - val erasedVCRef = eraseDerivedValueClassRef(tp) + val erasedVCRef = eraseDerivedValueClass(tp) if (erasedVCRef.exists) return sigName(erasedVCRef) } if (defn.isSyntheticFunctionClass(sym)) diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 89e085988131..20becf1cce35 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -84,7 +84,7 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] { ValueClassesMayNotContainInitalizationID, ValueClassesMayNotBeAbstractID, ValueClassesMayNotBeContaintedID, - ValueClassesMayNotWrapItselfID, + ValueClassesMayNotWrapAnotherValueClassID, ValueClassParameterMayNotBeAVarID, ValueClassNeedsExactlyOneValParamID, OnlyCaseClassOrCaseObjectAllowedID, diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 64cb29ac999b..20f97246b487 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1617,9 +1617,9 @@ import transform.SymUtils._ def explain = "" } - class ValueClassesMayNotWrapItself(valueClass: Symbol)(using Context) - extends SyntaxMsg(ValueClassesMayNotWrapItselfID) { - def msg = """A value class may not wrap itself""" + class ValueClassesMayNotWrapAnotherValueClass(valueClass: Symbol)(using Context) + extends SyntaxMsg(ValueClassesMayNotWrapAnotherValueClassID) { + def msg = """A value class may not wrap another user-defined value class""" def explain = "" } diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 42a63e53fc3c..5d63510e71d4 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -302,7 +302,6 @@ object Erasure { * in ExtensionMethods#transform. */ def cast(tree: Tree, pt: Type)(using Context): Tree = trace(i"cast ${tree.tpe.widen} --> $pt", show = true) { - def wrap(tycon: TypeRef) = ref(u2evt(tycon.typeSymbol.asClass)).appliedTo(tree) def unwrap(tycon: TypeRef) = @@ -357,7 +356,10 @@ object Erasure { if (pt.isInstanceOf[ProtoType] || tree.tpe <:< pt) tree else if (tpw.isErasedValueType) - adaptToType(box(tree), pt) + if (pt.isErasedValueType) then + tree.asInstance(pt) + else + adaptToType(box(tree), pt) else if (pt.isErasedValueType) adaptToType(unbox(tree, pt), pt) else if (tpw.isPrimitiveValueType && !pt.isPrimitiveValueType) diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index eb1f4816236e..3f6693440207 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -18,6 +18,7 @@ import NameOps._ import NameKinds.OuterSelectName import StdNames._ import NullOpsDecorator._ +import TypeUtils.isErasedValueType object FirstTransform { val name: String = "firstTransform" @@ -67,7 +68,7 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => qual.tpe } assert( - qualTpe.derivesFrom(tree.symbol.owner) || + qualTpe.isErasedValueType || qualTpe.derivesFrom(tree.symbol.owner) || tree.symbol.is(JavaStatic) && qualTpe.derivesFrom(tree.symbol.enclosingClass), i"non member selection of ${tree.symbol.showLocated} from ${qualTpe} in $tree") case _: TypeTree => diff --git a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala index 5b2592b5009b..1ec149d505be 100644 --- a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala +++ b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala @@ -212,12 +212,11 @@ object GenericSignatures { else if (sym == defn.UnitClass) jsig(defn.BoxedUnitClass.typeRef) else builder.append(defn.typeTag(sym.info)) else if (ValueClasses.isDerivedValueClass(sym)) { - val unboxed = ValueClasses.valueClassUnbox(sym.asClass).info.finalResultType - val unboxedSeen = tp.memberInfo(ValueClasses.valueClassUnbox(sym.asClass)).finalResultType - if (unboxedSeen.isPrimitiveValueType && !primitiveOK) + val erasedUnderlying = core.TypeErasure.fullErasure(tp) + if (erasedUnderlying.isPrimitiveValueType && !primitiveOK) classSig(sym, pre, args) else - jsig(unboxedSeen, toplevel, primitiveOK) + jsig(erasedUnderlying, toplevel, primitiveOK) } else if (defn.isSyntheticFunctionClass(sym)) { val erasedSym = defn.erasedFunctionClass(sym) diff --git a/compiler/src/dotty/tools/dotc/transform/ValueClasses.scala b/compiler/src/dotty/tools/dotc/transform/ValueClasses.scala index aba99f9d5b01..a86bf2c48fb5 100644 --- a/compiler/src/dotty/tools/dotc/transform/ValueClasses.scala +++ b/compiler/src/dotty/tools/dotc/transform/ValueClasses.scala @@ -13,7 +13,7 @@ import SymUtils._ /** Methods that apply to user-defined value classes */ object ValueClasses { - def isDerivedValueClass(sym: Symbol)(using Context): Boolean = { + def isDerivedValueClass(sym: Symbol)(using Context): Boolean = sym.isClass && { val d = sym.denot !d.isRefinementClass && d.isValueClass && @@ -54,15 +54,4 @@ object ValueClasses { /** The unboxed type that underlies a derived value class */ def underlyingOfValueClass(sym: ClassSymbol)(using Context): Type = valueClassUnbox(sym).info.resultType - - /** Whether a value class wraps itself */ - def isCyclic(cls: ClassSymbol)(using Context): Boolean = { - def recur(seen: Set[Symbol], clazz: ClassSymbol)(using Context): Boolean = - (seen contains clazz) || { - val unboxed = underlyingOfValueClass(clazz).typeSymbol - (isDerivedValueClass(unboxed)) && recur(seen + clazz, unboxed.asClass) - } - - recur(Set[Symbol](), cls) - } } diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index b31c14e60eee..1a50165eb0b7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -610,8 +610,8 @@ object Checking { report.error(ValueClassesMayNotBeAbstract(clazz), clazz.srcPos) if (!clazz.isStatic) report.error(ValueClassesMayNotBeContainted(clazz), clazz.srcPos) - if (isCyclic(clazz.asClass)) - report.error(ValueClassesMayNotWrapItself(clazz), clazz.srcPos) + if (isDerivedValueClass(underlyingOfValueClass(clazz.asClass).classSymbol)) + report.error(ValueClassesMayNotWrapAnotherValueClass(clazz), clazz.srcPos) else { val clParamAccessors = clazz.asClass.paramAccessors.filter { param => param.isTerm && !param.is(Flags.Accessor) diff --git a/sbt-dotty/sbt-test/scala2-compat/i8001/build.sbt b/sbt-dotty/sbt-test/scala2-compat/i8001/build.sbt new file mode 100644 index 000000000000..818af971469f --- /dev/null +++ b/sbt-dotty/sbt-test/scala2-compat/i8001/build.sbt @@ -0,0 +1,15 @@ +val scala3Version = sys.props("plugin.scalaVersion") +val scala2Version = "2.13.4" + +lazy val lib = (project in file ("lib")) + .settings(scalaVersion := scala2Version) + +lazy val test = (project in file ("main")) + .dependsOn(lib) + .settings( + scalaVersion := scala3Version, + // https://github.com/sbt/sbt/issues/5369 + projectDependencies := { + projectDependencies.value.map(_.withDottyCompat(scalaVersion.value)) + } + ) diff --git a/sbt-dotty/sbt-test/scala2-compat/i8001/lib/lib.scala b/sbt-dotty/sbt-test/scala2-compat/i8001/lib/lib.scala new file mode 100644 index 000000000000..1a80c43e839e --- /dev/null +++ b/sbt-dotty/sbt-test/scala2-compat/i8001/lib/lib.scala @@ -0,0 +1,43 @@ +class Poly[A](val value: A) extends AnyVal + +class Arr[A](val value: Array[A]) extends AnyVal + +class ArrRef[A <: AnyRef](val value: Array[A]) extends AnyVal + +class A { + def poly1(x: Poly[Int]): Poly[Int] = + new Poly(x.value) + + def poly2(x: Poly[String]): Poly[String] = + new Poly(x.value) + + def poly3(x: Poly[Array[Int]]): Poly[Array[Int]] = + new Poly(x.value) + + def poly4(x: Poly[Array[String]]): Poly[Array[String]] = + new Poly(x.value) + + def arr1(x: Arr[Int]): Arr[Int] = + new Arr(x.value) + + def arr2(x: Arr[String]): Arr[String] = + new Arr(x.value) + + def arr3(x: Arr[Array[Int]]): Arr[Array[Int]] = + new Arr(x.value) + + def arr4(x: Arr[Array[String]]): Arr[Array[String]] = + new Arr(x.value) + + def arrRef1(x: ArrRef[Integer]): ArrRef[Integer] = + new ArrRef(x.value) + + def arrRef2(x: ArrRef[String]): ArrRef[String] = + new ArrRef(x.value) + + def arrRef3(x: ArrRef[Array[Int]]): ArrRef[Array[Int]] = + new ArrRef(x.value) + + def arrRef4(x: ArrRef[Array[String]]): ArrRef[Array[String]] = + new ArrRef(x.value) +} diff --git a/sbt-dotty/sbt-test/scala2-compat/i8001/main/test.scala b/sbt-dotty/sbt-test/scala2-compat/i8001/main/test.scala new file mode 100644 index 000000000000..2ab27c68d429 --- /dev/null +++ b/sbt-dotty/sbt-test/scala2-compat/i8001/main/test.scala @@ -0,0 +1,20 @@ +object B { + def main(args: Array[String]): Unit = { + val a = new A + + a.poly1(new Poly(1)) + a.poly2(new Poly("")) + a.poly3(new Poly(Array(1))) + a.poly4(new Poly(Array(""))) + + a.arr1(new Arr(Array(1))) + a.arr2(new Arr(Array(""))) + a.arr3(new Arr(Array(Array(1)))) + a.arr4(new Arr(Array(Array("")))) + + a.arrRef1(new ArrRef(Array(1))) + a.arrRef2(new ArrRef(Array(""))) + a.arrRef3(new ArrRef(Array(Array(1)))) + a.arrRef4(new ArrRef(Array(Array("")))) + } +} diff --git a/sbt-dotty/sbt-test/scala2-compat/i8001/project/plugins.sbt b/sbt-dotty/sbt-test/scala2-compat/i8001/project/plugins.sbt new file mode 100644 index 000000000000..c17caab2d98c --- /dev/null +++ b/sbt-dotty/sbt-test/scala2-compat/i8001/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % sys.props("plugin.version")) diff --git a/sbt-dotty/sbt-test/scala2-compat/i8001/test b/sbt-dotty/sbt-test/scala2-compat/i8001/test new file mode 100644 index 000000000000..8aedcd64cf5b --- /dev/null +++ b/sbt-dotty/sbt-test/scala2-compat/i8001/test @@ -0,0 +1 @@ +> test/run diff --git a/tests/neg/i1642.scala b/tests/neg/i1642.scala index 2f7644698f0f..2d3216d66f02 100644 --- a/tests/neg/i1642.scala +++ b/tests/neg/i1642.scala @@ -1 +1,4 @@ -class Test2(val valueVal: Test2) extends AnyVal // error: value class cannot wrap itself +class Test0(val valueVal: Test0) extends AnyVal // error: value class cannot wrap itself + +class Test1(val x: Int) extends AnyVal +class Test2(val y: Test1) extends AnyVal // error: value class may not wrap another user-defined value class diff --git a/tests/neg/i5005.scala b/tests/neg/i5005.scala index 5dfcfc37b1a3..1734f79d934c 100644 --- a/tests/neg/i5005.scala +++ b/tests/neg/i5005.scala @@ -1,5 +1,5 @@ -case class i0 (i0: i1) extends AnyVal +case class i0 (i0: i1) extends AnyVal // error trait i1 extends i0 // error trait F[x] extends AnyVal // error diff --git a/tests/pos/i1642.scala b/tests/pos/i1642.scala deleted file mode 100644 index 2fe67cf18af7..000000000000 --- a/tests/pos/i1642.scala +++ /dev/null @@ -1,2 +0,0 @@ -class Test1(val x: Int) extends AnyVal -class Test2(val y: Test1) extends AnyVal diff --git a/tests/run/i8001/A_1.scala b/tests/run/i8001/A_1.scala new file mode 100644 index 000000000000..db6e6c0a3dd9 --- /dev/null +++ b/tests/run/i8001/A_1.scala @@ -0,0 +1,71 @@ +trait TC[A] { + def apply(a: A): Unit +} + +class Poly[A](val value: A) extends AnyVal + +class Arr[A](val value: Array[A]) extends AnyVal + +class ArrRef[A <: AnyRef](val value: Array[A]) extends AnyVal + +class A { + val tc = new TC[Poly[String]] { + def apply(a: Poly[String]): Unit = () + } + + def poly1(x: Poly[Int]): Poly[Int] = + new Poly(x.value) + + def poly2(x: Poly[String]): Poly[String] = + new Poly(x.value) + + def poly3(x: Poly[Array[Int]]): Poly[Array[Int]] = + new Poly(x.value) + + def poly4(x: Poly[Array[String]]): Poly[Array[String]] = + new Poly(x.value) + + def arr1(x: Arr[Int]): Arr[Int] = + new Arr(x.value) + + def arr2(x: Arr[String]): Arr[String] = + new Arr(x.value) + + def arr3(x: Arr[Array[Int]]): Arr[Array[Int]] = + new Arr(x.value) + + def arr4(x: Arr[Array[String]]): Arr[Array[String]] = + new Arr(x.value) + + def arrRef1(x: ArrRef[Integer]): ArrRef[Integer] = + new ArrRef(x.value) + + def arrRef2(x: ArrRef[String]): ArrRef[String] = + new ArrRef(x.value) + + def arrRef3(x: ArrRef[Array[Int]]): ArrRef[Array[Int]] = + new ArrRef(x.value) + + def arrRef4(x: ArrRef[Array[String]]): ArrRef[Array[String]] = + new ArrRef(x.value) + + + def test: Unit = { + tc.apply(new Poly("")) + + poly1(new Poly(1)) + poly2(new Poly("")) + poly3(new Poly(Array(1))) + poly4(new Poly(Array(""))) + + arr1(new Arr(Array(1))) + arr2(new Arr(Array(""))) + arr3(new Arr(Array(Array(1)))) + arr4(new Arr(Array(Array("")))) + + arrRef1(new ArrRef(Array(1))) + arrRef2(new ArrRef(Array(""))) + arrRef3(new ArrRef(Array(Array(1)))) + arrRef4(new ArrRef(Array(Array("")))) + } +} diff --git a/tests/run/i8001/B_2.java b/tests/run/i8001/B_2.java new file mode 100644 index 000000000000..bde79bae36fb --- /dev/null +++ b/tests/run/i8001/B_2.java @@ -0,0 +1,27 @@ +public class B_2 { + public static void test() { + A a = new A(); + a.test(); + + int[] intArr = { 1 }; + int[][] intArr2 = { { 1 } }; + String[] stringArr = { "" }; + String[][] stringArr2 = { { "" } }; + Integer[] integerArr = { 1 }; + + a.poly1(1); + a.poly2(""); + a.poly3(intArr); + a.poly4(stringArr); + + a.arr1(intArr); + a.arr2(stringArr); + a.arr3(intArr); + a.arr4(stringArr2); + + a.arrRef1(integerArr); + a.arrRef2(stringArr); + a.arrRef3(intArr2); + a.arrRef4(stringArr2); + } +} diff --git a/tests/run/i8001/Test_3.scala b/tests/run/i8001/Test_3.scala new file mode 100644 index 000000000000..b397d63ff166 --- /dev/null +++ b/tests/run/i8001/Test_3.scala @@ -0,0 +1,4 @@ +object Test { + def main(args: Array[String]): Unit = + B_2.test() +} diff --git a/tests/run/t7685-class-simple.scala b/tests/run/t7685-class-simple.scala deleted file mode 100644 index 5147a66c0543..000000000000 --- a/tests/run/t7685-class-simple.scala +++ /dev/null @@ -1,10 +0,0 @@ -object Test { - final class Foo(val x: String) extends AnyVal { override def toString = "" + x } - final class Bar(val f: Foo) extends AnyVal { override def toString = "" + f } - def main(args: Array[String]) = { - val x = "abc" - val f = new Foo(x) - val b = new Bar(f) - assert(b.toString == "abc") - } -} From f91fb3640bbd1f6ecf9dc3fbfa9d6f5f9c9a83db Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Sun, 13 Dec 2020 02:48:34 +0100 Subject: [PATCH 2/3] Disable "class scala" test After the previous commit, this randomly fails the CI with "class defined twice module class scala" and I don't want to add more special cases to support this, instead we should probably just disallow `scala` as a top-level classname. --- tests/{pos => disabled}/i5033a.scala | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{pos => disabled}/i5033a.scala (100%) diff --git a/tests/pos/i5033a.scala b/tests/disabled/i5033a.scala similarity index 100% rename from tests/pos/i5033a.scala rename to tests/disabled/i5033a.scala From 4727f44068b84347df832952b79b9a93c918d3a7 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Sun, 13 Dec 2020 02:51:49 +0100 Subject: [PATCH 3/3] Remove MissingCoreLibTests This test printed an error message in the log output which was extremely misleading since it looked like a weird test failure. Removing it instead of fixing it because it's not actually testing anything useful: it's supposed to check that we don't get a cascade of error messages if scala3-library is missing from the classpath, but after 24eb6cf13d603ff41511f85bb20147c5223bb656 this only worked by chance because the code we're compiling is extremely simple (Foo.scala only contains "class Foo"), so it's doubly misleading. --- .../tools/dotc/MissingCoreLibTests.scala | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 compiler/test/dotty/tools/dotc/MissingCoreLibTests.scala diff --git a/compiler/test/dotty/tools/dotc/MissingCoreLibTests.scala b/compiler/test/dotty/tools/dotc/MissingCoreLibTests.scala deleted file mode 100644 index f50e66cb49eb..000000000000 --- a/compiler/test/dotty/tools/dotc/MissingCoreLibTests.scala +++ /dev/null @@ -1,24 +0,0 @@ -package dotty -package tools -package dotc - -import org.junit.Test -import org.junit.Assert._ - -import vulpix.TestConfiguration.mkClasspath - -class MissingCoreLibTests { - - @Test def missingDottyLib: Unit = { - val classPath = mkClasspath(List(Properties.scalaLibrary)) // missing Properties.dottyLibrary - val source = "tests/pos/Foo.scala" - val options = Array("-classpath", classPath, source) - val reporter = Main.process(options) - assertEquals(1, reporter.errorCount) - val errorMessage = reporter.allErrors.head.message - // FIXME: We currently only detect if the scala library is missing but not the dotty library. - // See dotty.tools.dotc.MissingCoreLibraryException - // assertTrue(errorMessage.contains("Make sure the compiler core libraries are on the classpath")) - } - -}