From f080a39a91ecf4dfff37380212fda4dbf76c59d2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 13 Aug 2018 17:51:08 +0200 Subject: [PATCH 01/17] Add Tuple and *: types These types are injected as parents of Tuple1,...,Tuple22, but are eliminated again at erasure. To support them properly before full dotty bootstrap we needed a @`rewrite` annotation that erases the annotated method (unlike `@forceInline`, which generates code). --- .../dotty/tools/dotc/core/Definitions.scala | 36 +++++++++++++++++-- .../dotty/tools/dotc/core/TypeErasure.scala | 13 ++++--- .../dotty/tools/dotc/transform/Erasure.scala | 17 +++++---- .../src/dotty/tools/dotc/typer/Namer.scala | 3 +- .../src/dotty/tools/dotc/typer/Typer.scala | 7 ++-- library/src/scala/Tuple.scala | 14 ++++++++ library/src/scala/forceInline.scala | 3 +- library/src/scala/rewrite.scala | 16 +++++++++ 8 files changed, 87 insertions(+), 22 deletions(-) create mode 100644 library/src/scala/Tuple.scala create mode 100644 library/src/scala/rewrite.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index e744e1c08d66..04813479d5cf 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -706,6 +706,12 @@ class Definitions { lazy val XMLTopScopeModuleRef = ctx.requiredModuleRef("scala.xml.TopScope") + lazy val TupleTypeRef = ctx.requiredClassRef("scala.Tuple") + def TupleClass(implicit ctx: Context) = TupleTypeRef.symbol.asClass + + lazy val PairType = ctx.requiredClassRef("scala.*:") + def PairClass(implicit ctx: Context) = PairType.symbol.asClass + // Annotation base classes lazy val AnnotationType = ctx.requiredClassRef("scala.annotation.Annotation") def AnnotationClass(implicit ctx: Context) = AnnotationType.symbol.asClass @@ -737,6 +743,8 @@ class Definitions { def ImplicitNotFoundAnnot(implicit ctx: Context) = ImplicitNotFoundAnnotType.symbol.asClass lazy val ForceInlineAnnotType = ctx.requiredClassRef("scala.forceInline") def ForceInlineAnnot(implicit ctx: Context) = ForceInlineAnnotType.symbol.asClass + lazy val RewriteAnnotType = ctx.requiredClassRef("scala.rewrite") + def RewriteAnnot(implicit ctx: Context) = RewriteAnnotType.symbol.asClass lazy val TransparentParamAnnotType = ctx.requiredClassRef("scala.annotation.internal.TransparentParam") def TransparentParamAnnot(implicit ctx: Context) = TransparentParamAnnotType.symbol.asClass lazy val InvariantBetweenAnnotType = ctx.requiredClassRef("scala.annotation.internal.InvariantBetween") @@ -880,7 +888,7 @@ class Definitions { private lazy val ImplementedFunctionType = mkArityArray("scala.Function", MaxImplementedFunctionArity, 0) def FunctionClassPerRun = new PerRun[Array[Symbol]](implicit ctx => ImplementedFunctionType.map(_.symbol.asClass)) - lazy val TupleType = mkArityArray("scala.Tuple", MaxTupleArity, 2) + lazy val TupleType = mkArityArray("scala.Tuple", MaxTupleArity, 1) def FunctionClass(n: Int, isImplicit: Boolean = false, isErased: Boolean = false)(implicit ctx: Context) = if (isImplicit && isErased) @@ -901,8 +909,6 @@ class Definitions { if (n <= MaxImplementedFunctionArity && (!isImplicit || ctx.erasedTypes) && !isErased) ImplementedFunctionType(n) else FunctionClass(n, isImplicit, isErased).typeRef - private lazy val TupleTypes: Set[TypeRef] = TupleType.toSet - /** If `cls` is a class in the scala package, its name, otherwise EmptyTypeName */ def scalaClassName(cls: Symbol)(implicit ctx: Context): TypeName = if (cls.isClass && cls.owner == ScalaPackageClass) cls.asClass.name else EmptyTypeName @@ -1199,6 +1205,8 @@ class Definitions { def isValueSubClass(sym1: Symbol, sym2: Symbol) = valueTypeEnc(sym2.asClass.name) % valueTypeEnc(sym1.asClass.name) == 0 + lazy val erasedToObject = Set[Symbol](AnyClass, AnyValClass, TupleClass, PairClass, SingletonClass) + // ----- Initialization --------------------------------------------------- /** Lists core classes that don't have underlying bytecode, but are synthesized on-the-fly in every reflection universe */ @@ -1226,6 +1234,24 @@ class Definitions { private[this] var isInitialized = false + def fixTupleCompleter(cls: ClassSymbol): Unit = cls.infoOrCompleter match { + case completer: LazyType => + cls.info = new LazyType { + def syntheticParent(tparams: List[TypeSymbol]): Type = + if (tparams.isEmpty) TupleTypeRef + else (tparams :\ (UnitType: Type)) ((tparam, tail) => PairType.appliedTo(tparam.typeRef, tail)) + override def complete(denot: SymDenotation)(implicit ctx: Context) = { + completer.complete(denot) + denot.info match { + case info: ClassInfo => + denot.info = info.derivedClassInfo( + classParents = info.classParents :+ syntheticParent(cls.typeParams)) + } + } + } + case _ => + } + def init()(implicit ctx: Context) = { this.ctx = ctx if (!isInitialized) { @@ -1243,6 +1269,10 @@ class Definitions { // force initialization of every symbol that is synthesized or hijacked by the compiler val forced = syntheticCoreClasses ++ syntheticCoreMethods ++ ScalaValueClasses() + fixTupleCompleter(UnitClass) + for (i <- 1 to MaxTupleArity) + fixTupleCompleter(TupleType(i).symbol.asClass) + isInitialized = true } } diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index be59b0eb8849..40186a1bedcb 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -44,7 +44,8 @@ object TypeErasure { case tp: TypeRef => val sym = tp.symbol sym.isClass && - sym != defn.AnyClass && sym != defn.ArrayClass && + sym != defn.ArrayClass && + !defn.erasedToObject.contains(sym) && !defn.isSyntheticFunctionClass(sym) case _: TermRef => true @@ -280,10 +281,8 @@ object TypeErasure { // Pick the last minimum to prioritise classes over traits minimums.lastOption match { - case Some(lub) if lub != defn.AnyClass && lub != defn.AnyValClass => - lub.typeRef - case _ => // Any/AnyVal only exist before erasure - defn.ObjectType + case Some(lub) => valueErasure(lub.typeRef) + case _ => defn.ObjectType } } } @@ -354,7 +353,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean * - otherwise, if T is a type parameter coming from Java, []Object * - otherwise, Object * - For a term ref p.x, the type # x. - * - For a typeref scala.Any, scala.AnyVal or scala.Singleton: |java.lang.Object| + * - For a typeref scala.Any, scala.AnyVal, scala.Singleton, scala.Tuple, or scala.*: : |java.lang.Object| * - For a typeref scala.Unit, |scala.runtime.BoxedUnit|. * - For a typeref scala.FunctionN, where N > MaxImplementedFunctionArity, scala.FunctionXXL * - For a typeref scala.ImplicitFunctionN, | scala.FunctionN | @@ -500,7 +499,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean private def normalizeClass(cls: ClassSymbol)(implicit ctx: Context): ClassSymbol = { if (cls.owner == defn.ScalaPackageClass) { - if (cls == defn.AnyClass || cls == defn.AnyValClass || cls == defn.SingletonClass) + if (defn.erasedToObject.contains(cls)) return defn.ObjectClass if (cls == defn.UnitClass) return defn.BoxedUnitClass diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 69bded663ee5..df44b9974810 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -135,12 +135,15 @@ class Erasure extends Phase with DenotTransformer { } } - def assertErased(tp: Type, tree: tpd.Tree = tpd.EmptyTree)(implicit ctx: Context): Unit = - if (tp.typeSymbol == defn.ArrayClass && - ctx.compilationUnit.source.file.name == "Array.scala") {} // ok - else - assert(isErasedType(tp), + def assertErased(tp: Type, tree: tpd.Tree = tpd.EmptyTree)(implicit ctx: Context): Unit = { + def isAllowed(cls: Symbol, sourceName: String) = + tp.typeSymbol == cls && ctx.compilationUnit.source.file.name == sourceName + assert(isErasedType(tp) || + isAllowed(defn.ArrayClass, "Array.scala") || + isAllowed(defn.TupleClass, "Tuple.scala") || + isAllowed(defn.PairClass, "Tuple.scala"), i"The type $tp - ${tp.toString} of class ${tp.getClass} of tree $tree : ${tree.tpe} / ${tree.getClass} is illegal after erasure, phase = ${ctx.phase.prev}") + } } object Erasure { @@ -376,7 +379,7 @@ object Erasure { * on selections `e.m`, where `OT` is the type of the owner of `m` and `ET` * is the erased type of the selection's original qualifier expression. * - * e.m1 -> e.m2 if `m1` is a member of Any or AnyVal and `m2` is + * e.m1 -> e.m2 if `m1` is a member of a class that erases to object and `m2` is * the same-named member in Object. * e.m -> box(e).m if `e` is primitive and `m` is a member or a reference class * or `e` has an erased value class type. @@ -396,7 +399,7 @@ object Erasure { def mapOwner(sym: Symbol): Symbol = { def recur(owner: Symbol): Symbol = - if ((owner eq defn.AnyClass) || (owner eq defn.AnyValClass)) { + if (defn.erasedToObject.contains(owner)) { assert(sym.isConstructor, s"${sym.showLocated}") defn.ObjectClass } else if (defn.isSyntheticFunctionClass(owner)) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 391c96185d19..bbcb7b3de460 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -753,7 +753,8 @@ class Namer { typer: Typer => val cls = typedAheadAnnotationClass(annotTree)(annotCtx) val ann = Annotation.deferred(cls, implicit ctx => typedAnnotation(annotTree)) sym.addAnnotation(ann) - if (cls == defn.ForceInlineAnnot && sym.is(Method, butNot = Accessor)) + if ((cls == defn.ForceInlineAnnot || cls == defn.RewriteAnnot) && + sym.is(Method, butNot = Accessor)) sym.setFlag(Rewrite) } case _ => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6be9008ca0dc..b78b3acde637 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1523,9 +1523,10 @@ class Typer extends Namer def typedParent(tree: untpd.Tree): Tree = { var result = if (tree.isType) typedType(tree)(superCtx) else typedExpr(tree)(superCtx) val psym = result.tpe.dealias.typeSymbol - if (seenParents.contains(psym) && !cls.isRefinementClass) - ctx.error(i"$psym is extended twice", tree.pos) - seenParents += psym + if (seenParents.contains(psym) && !cls.isRefinementClass) { + if (!ctx.isAfterTyper) ctx.error(i"$psym is extended twice", tree.pos) + } + else seenParents += psym if (tree.isType) { if (psym.is(Trait) && !cls.is(Trait) && !cls.superClass.isSubClass(psym)) result = maybeCall(result, psym, psym.primaryConstructor.info) diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala new file mode 100644 index 000000000000..1a36e841567e --- /dev/null +++ b/library/src/scala/Tuple.scala @@ -0,0 +1,14 @@ +package scala +import annotation.showAsInfix + +sealed trait Tuple extends Any + +@showAsInfix +sealed class *:[+H, +T <: Tuple] extends Tuple { + @`rewrite` def head: H = ??? + @`rewrite` def tail: T = ??? +} + +object *: { + @`rewrite` def unapply[H, T <: Tuple](x: H *: T) = Some((x.head, x.tail)) +} diff --git a/library/src/scala/forceInline.scala b/library/src/scala/forceInline.scala index 08eb0e59462a..577ec48606e6 100644 --- a/library/src/scala/forceInline.scala +++ b/library/src/scala/forceInline.scala @@ -1,6 +1,7 @@ package scala -/** An annotation on methods that is equivalent to Dotty `inline` modifier. +/** An annotation on methods that is equivalent to Dotty `rewrite` modifier, + * except that it does not imply `erased`. * * The annotation should be used instead of the `inline` modifier in code * that needs to cross compile between Scala 2 and Dotty. diff --git a/library/src/scala/rewrite.scala b/library/src/scala/rewrite.scala new file mode 100644 index 000000000000..7e3914e851f5 --- /dev/null +++ b/library/src/scala/rewrite.scala @@ -0,0 +1,16 @@ +package scala + +/** An annotation on methods that is equivalent to Dotty `rewrite` modifier. + * + * The annotation should be used instead of the `rewrite` modifier in code + * that needs to cross compile between Scala 2 and Dotty. + * + * Note that Scala 2 ignores the `@rewrite` annotation, and one must use + * both the `@inline` and `@rewrite` annotation to inline across the + * two compilers. E.g. + * + * ```scala + * @inline @`rewrite` def foo = ... + * ``` + */ +class `rewrite` extends scala.annotation.StaticAnnotation From 84baabb4775c8a18c657a03174a1336160f8dc78 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Tue, 14 Aug 2018 18:34:27 +0900 Subject: [PATCH 02/17] Add version-specific source directories in the library And replace Tuple.scala with a Scala 2 implementation and a Scala 3 implementation. The Scala 2 implementaion of Tuple.scala is not strictly necessary but could be useful if non-version-specific library sources end up referencing scala.Tuple. --- .../dotty/tools/dotc/CompilationTests.scala | 5 +++- library/{src => src-scala2}/scala/Tuple.scala | 6 ++--- library/src-scala3/scala/Tuple.scala | 26 +++++++++++++++++++ project/Build.scala | 12 ++++++++- 4 files changed, 44 insertions(+), 5 deletions(-) rename library/{src => src-scala2}/scala/Tuple.scala (53%) create mode 100644 library/src-scala3/scala/Tuple.scala diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 5fcca127e27f..005c95099377 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -211,8 +211,11 @@ class CompilationTests extends ParallelTesting { Array("-Ycheck-reentrant", "-Yemit-tasty-in-class") ) + val libraryDirs = List(Paths.get("library/src"), Paths.get("library/src-scala3")) + val librarySources = libraryDirs.flatMap(d => sources(Files.walk(d))) + val lib = - compileDir("library/src", + compileList("src", librarySources, defaultOptions.and("-Ycheck-reentrant", "-strict", "-priorityclasspath", defaultOutputDir))(libGroup) val compilerDir = Paths.get("compiler/src") diff --git a/library/src/scala/Tuple.scala b/library/src-scala2/scala/Tuple.scala similarity index 53% rename from library/src/scala/Tuple.scala rename to library/src-scala2/scala/Tuple.scala index 1a36e841567e..febee0abd704 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src-scala2/scala/Tuple.scala @@ -5,10 +5,10 @@ sealed trait Tuple extends Any @showAsInfix sealed class *:[+H, +T <: Tuple] extends Tuple { - @`rewrite` def head: H = ??? - @`rewrite` def tail: T = ??? + def head: H = ??? + def tail: T = ??? } object *: { - @`rewrite` def unapply[H, T <: Tuple](x: H *: T) = Some((x.head, x.tail)) + def unapply[H, T <: Tuple](x: H *: T) = Some((x.head, x.tail)) } diff --git a/library/src-scala3/scala/Tuple.scala b/library/src-scala3/scala/Tuple.scala new file mode 100644 index 000000000000..e53926ae4600 --- /dev/null +++ b/library/src-scala3/scala/Tuple.scala @@ -0,0 +1,26 @@ +package scala +import annotation.showAsInfix + +object typelevel { + erased def erasedValue[T]: T = ??? +} + +import typelevel._ + +sealed trait Tuple extends Any + +@showAsInfix +sealed class *:[+H, +T <: Tuple] extends Tuple { + rewrite def head: H = ??? + rewrite def tail: T = ??? + + rewrite private def _size(xs: Tuple): Int = //rewrite + xs match { + case _: Unit => 0 + case _: *:[_, xs1] => _size(erasedValue[xs1]) + 1 + } +} + +object *: { + rewrite def unapply[H, T <: Tuple](x: H *: T) = Some((x.head, x.tail)) +} diff --git a/project/Build.scala b/project/Build.scala index 6e9deefbb3d0..60f909a826b7 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -749,7 +749,17 @@ object Build { // Settings shared between dotty-library and dotty-library-bootstrapped lazy val dottyLibrarySettings = Seq( - libraryDependencies += "org.scala-lang" % "scala-library" % scalacVersion + libraryDependencies += "org.scala-lang" % "scala-library" % scalacVersion, + // Add version-specific source directories: + // - files in src-scala3 will only be compiled by dotty + // - files in src-scala2 will only be compiled by scalac + unmanagedSourceDirectories in Compile += { + val baseDir = baseDirectory.value + if (isDotty.value) + baseDir / "src-scala3" + else + baseDir / "src-scala2" + } ) lazy val `dotty-library` = project.in(file("library")).asDottyLibrary(NonBootstrapped) From 4e550abd3529a4043a82d986679f3e61b14ec09b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 14 Aug 2018 15:10:29 +0200 Subject: [PATCH 03/17] Fix Inlined abbreviation in erased code Erased code does not keep full Inlined trees. The previous version converted the Inlined tree back to the call, but this can expose leaks (since calls are not traversed for references). We now keep the Inlined node, so that its call part can be subsequently simplified. --- compiler/src/dotty/tools/dotc/transform/PostTyper.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index d12420f1313b..e89daa24de95 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -178,7 +178,8 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase private object dropInlines extends TreeMap { override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { - case Inlined(call, _, _) => Typed(call, TypeTree(tree.tpe)) + case Inlined(call, _, _) => + cpy.Inlined(tree)(call, Nil, Typed(ref(defn.Predef_undefined), TypeTree(tree.tpe))) case _ => super.transform(tree) } } From 6dde9ea7f5579b157f779973a9d072a6b904752f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 14 Aug 2018 15:13:02 +0200 Subject: [PATCH 04/17] Remove rewrite annotation again With the Scala 2/3 split, we don't need it anymore. --- .../src/dotty/tools/dotc/core/Definitions.scala | 7 ++++--- compiler/src/dotty/tools/dotc/typer/Namer.scala | 3 +-- library/src/scala/rewrite.scala | 16 ---------------- 3 files changed, 5 insertions(+), 21 deletions(-) delete mode 100644 library/src/scala/rewrite.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 04813479d5cf..daf063fc3460 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -743,8 +743,6 @@ class Definitions { def ImplicitNotFoundAnnot(implicit ctx: Context) = ImplicitNotFoundAnnotType.symbol.asClass lazy val ForceInlineAnnotType = ctx.requiredClassRef("scala.forceInline") def ForceInlineAnnot(implicit ctx: Context) = ForceInlineAnnotType.symbol.asClass - lazy val RewriteAnnotType = ctx.requiredClassRef("scala.rewrite") - def RewriteAnnot(implicit ctx: Context) = RewriteAnnotType.symbol.asClass lazy val TransparentParamAnnotType = ctx.requiredClassRef("scala.annotation.internal.TransparentParam") def TransparentParamAnnot(implicit ctx: Context) = TransparentParamAnnotType.symbol.asClass lazy val InvariantBetweenAnnotType = ctx.requiredClassRef("scala.annotation.internal.InvariantBetween") @@ -1234,6 +1232,9 @@ class Definitions { private[this] var isInitialized = false + /** Add a `Tuple` as a parent to `Unit`. + * Add the right `*:` instance as a parent to Tuple1..Tuple22 + */ def fixTupleCompleter(cls: ClassSymbol): Unit = cls.infoOrCompleter match { case completer: LazyType => cls.info = new LazyType { @@ -1271,7 +1272,7 @@ class Definitions { fixTupleCompleter(UnitClass) for (i <- 1 to MaxTupleArity) - fixTupleCompleter(TupleType(i).symbol.asClass) + fixTupleCompleter(TupleType(i).symbol.asClass) isInitialized = true } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index bbcb7b3de460..391c96185d19 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -753,8 +753,7 @@ class Namer { typer: Typer => val cls = typedAheadAnnotationClass(annotTree)(annotCtx) val ann = Annotation.deferred(cls, implicit ctx => typedAnnotation(annotTree)) sym.addAnnotation(ann) - if ((cls == defn.ForceInlineAnnot || cls == defn.RewriteAnnot) && - sym.is(Method, butNot = Accessor)) + if (cls == defn.ForceInlineAnnot && sym.is(Method, butNot = Accessor)) sym.setFlag(Rewrite) } case _ => diff --git a/library/src/scala/rewrite.scala b/library/src/scala/rewrite.scala deleted file mode 100644 index 7e3914e851f5..000000000000 --- a/library/src/scala/rewrite.scala +++ /dev/null @@ -1,16 +0,0 @@ -package scala - -/** An annotation on methods that is equivalent to Dotty `rewrite` modifier. - * - * The annotation should be used instead of the `rewrite` modifier in code - * that needs to cross compile between Scala 2 and Dotty. - * - * Note that Scala 2 ignores the `@rewrite` annotation, and one must use - * both the `@inline` and `@rewrite` annotation to inline across the - * two compilers. E.g. - * - * ```scala - * @inline @`rewrite` def foo = ... - * ``` - */ -class `rewrite` extends scala.annotation.StaticAnnotation From 75729c8381dcbc61bdaa005429b6a540ca513dd5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 14 Aug 2018 15:22:36 +0200 Subject: [PATCH 05/17] Add generative operations to tuples --- library/src-scala2/scala/Tuple.scala | 10 +- library/src-scala3/scala/Tuple.scala | 143 ++++++++++++++++-- .../src-scala3/scala/typelevel/package.scala | 6 + library/src/scala/TupleXXL.scala | 14 ++ tests/run/Tuple.scala | 1 - tests/run/tuples1.check | 13 ++ tests/run/tuples1.scala | 37 +++++ 7 files changed, 206 insertions(+), 18 deletions(-) create mode 100644 library/src-scala3/scala/typelevel/package.scala create mode 100644 library/src/scala/TupleXXL.scala create mode 100644 tests/run/tuples1.check create mode 100644 tests/run/tuples1.scala diff --git a/library/src-scala2/scala/Tuple.scala b/library/src-scala2/scala/Tuple.scala index febee0abd704..7c981cc592ea 100644 --- a/library/src-scala2/scala/Tuple.scala +++ b/library/src-scala2/scala/Tuple.scala @@ -2,13 +2,9 @@ package scala import annotation.showAsInfix sealed trait Tuple extends Any +object Tuple @showAsInfix -sealed class *:[+H, +T <: Tuple] extends Tuple { - def head: H = ??? - def tail: T = ??? -} +sealed class *:[+H, +T <: Tuple] extends Tuple -object *: { - def unapply[H, T <: Tuple](x: H *: T) = Some((x.head, x.tail)) -} +object *: \ No newline at end of file diff --git a/library/src-scala3/scala/Tuple.scala b/library/src-scala3/scala/Tuple.scala index e53926ae4600..ae4e30c6dbe7 100644 --- a/library/src-scala3/scala/Tuple.scala +++ b/library/src-scala3/scala/Tuple.scala @@ -1,24 +1,147 @@ package scala import annotation.showAsInfix +import typelevel._ + +sealed trait Tuple extends Any { + import Tuple._ + rewrite def toArray: Array[Object] = rewrite _size(this) match { + case 0 => + $emptyArray + case 1 => + val t = asInstanceOf[Tuple1[Object]] + Array(t._1) + case 2 => + val t = asInstanceOf[Tuple2[Object, Object]] + Array(t._1, t._2) + case 3 => + val t = asInstanceOf[Tuple3[Object, Object, Object]] + Array(t._1, t._2, t._3) + case 4 => + val t = asInstanceOf[Tuple4[Object, Object, Object, Object]] + Array(t._1, t._2, t._3, t._4) + case n if n <= $MaxSpecialized => + $toArray(this, n) + case n => + asInstanceOf[TupleXXL].elems + } -object typelevel { - erased def erasedValue[T]: T = ??? + rewrite def *: [H] (x: H): Tuple = { + erased val resTpe = Typed(_pair(x, this)) + rewrite _size(this) match { + case 0 => + Tuple1(x).asInstanceOf[resTpe.Type] + case 1 => + Tuple2(x, asInstanceOf[Tuple1[_]]._1).asInstanceOf[resTpe.Type] + case 2 => + val t = asInstanceOf[Tuple2[_, _]] + Tuple3(x, t._1, t._2).asInstanceOf[resTpe.Type] + case 3 => + val t = asInstanceOf[Tuple3[_, _, _]] + Tuple4(x, t._1, t._2, t._3).asInstanceOf[resTpe.Type] + case 4 => + val t = asInstanceOf[Tuple4[_, _, _, _]] + Tuple5(x, t._1, t._2, t._3, t._4).asInstanceOf[resTpe.Type] + case n => + fromArray[resTpe.Type]($consArray(x, toArray)) + } + } } -import typelevel._ +object Tuple { + transparent val $MaxSpecialized = 22 -sealed trait Tuple extends Any + val $emptyArray = Array[Object]() -@showAsInfix -sealed class *:[+H, +T <: Tuple] extends Tuple { - rewrite def head: H = ??? - rewrite def tail: T = ??? + def $toArray(xs: Tuple, n: Int) = { + val arr = new Array[Object](n) + var i = 0 + var it = xs.asInstanceOf[Product].productIterator + while (i < n) { + arr(i) = it.next().asInstanceOf[Object] + i += 1 + } + arr + } + + def $consArray[H](x: H, elems: Array[Object]): Array[Object] = { + val elems1 = new Array[Object](elems.length + 1) + elems1(0) = x.asInstanceOf[Object] + Array.copy(elems, 0, elems1, 1, elems.length) + elems1 + } - rewrite private def _size(xs: Tuple): Int = //rewrite - xs match { + private[scala] rewrite def _pair[H, T <: Tuple] (x: H, xs: T): Tuple = + erasedValue[H *: T] + + private[scala] rewrite def _size(xs: Tuple): Int = + rewrite xs match { case _: Unit => 0 case _: *:[_, xs1] => _size(erasedValue[xs1]) + 1 } + + private[scala] rewrite def _head(xs: Tuple): Any = rewrite xs match { + case _: (x *: _) => erasedValue[x] + } + + rewrite def fromArray[T <: Tuple](xs: Array[Object]): T = + rewrite _size(erasedValue[T]) match { + case 0 => ().asInstanceOf[T] + case 1 => Tuple1(xs(0)).asInstanceOf[T] + case 2 => Tuple2(xs(0), xs(1)).asInstanceOf[T] + case 3 => Tuple3(xs(0), xs(1), xs(2)).asInstanceOf[T] + case 4 => Tuple4(xs(0), xs(1), xs(2), xs(3)).asInstanceOf[T] + case 5 => Tuple5(xs(0), xs(1), xs(2), xs(3), xs(4)).asInstanceOf[T] + case 6 => Tuple6(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5)).asInstanceOf[T] + case 7 => Tuple7(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6)).asInstanceOf[T] + case 8 => Tuple8(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7)).asInstanceOf[T] + case 9 => Tuple9(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8)).asInstanceOf[T] + case 10 => Tuple10(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9)).asInstanceOf[T] + case 11 => Tuple11(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10)).asInstanceOf[T] + case 12 => Tuple12(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11)).asInstanceOf[T] + case 13 => Tuple13(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11), xs(12)).asInstanceOf[T] + case 14 => Tuple14(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11), xs(12), xs(13)).asInstanceOf[T] + case 15 => Tuple15(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11), xs(12), xs(13), xs(14)).asInstanceOf[T] + case 16 => Tuple16(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11), xs(12), xs(13), xs(14), xs(15)).asInstanceOf[T] + case 17 => Tuple17(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11), xs(12), xs(13), xs(14), xs(15), xs(16)).asInstanceOf[T] + case 18 => Tuple18(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11), xs(12), xs(13), xs(14), xs(15), xs(16), xs(17)).asInstanceOf[T] + case 19 => Tuple19(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11), xs(12), xs(13), xs(14), xs(15), xs(16), xs(17), xs(18)).asInstanceOf[T] + case 20 => Tuple20(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11), xs(12), xs(13), xs(14), xs(15), xs(16), xs(17), xs(18), xs(19)).asInstanceOf[T] + case 21 => Tuple21(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11), xs(12), xs(13), xs(14), xs(15), xs(16), xs(17), xs(18), xs(19), xs(20)).asInstanceOf[T] + case 22 => Tuple22(xs(0), xs(1), xs(2), xs(3), xs(4), xs(5), xs(6), xs(7), xs(8), xs(9), xs(10), xs(11), xs(12), xs(13), xs(14), xs(15), xs(16), xs(17), xs(18), xs(19), xs(20), xs(21)).asInstanceOf[T] + case _ => TupleXXL(xs).asInstanceOf[T] + } +} + +@showAsInfix +sealed class *:[+H, +T <: Tuple] extends Tuple { + import Tuple._ + + rewrite def head: Any = { + erased val resTpe = Typed(_head(this)) + val resVal = rewrite _size(this) match { + case 1 => + val t = asInstanceOf[Tuple1[_]] + t._1 + case 2 => + val t = asInstanceOf[Tuple2[_, _]] + t._1 + case 3 => + val t = asInstanceOf[Tuple3[_, _, _]] + t._1 + case 4 => + val t = asInstanceOf[Tuple4[_, _, _, _]] + t._1 + case n if n > 4 && n <= $MaxSpecialized => + asInstanceOf[Product].productElement(0) + case n if n > $MaxSpecialized => + val t = asInstanceOf[TupleXXL] + t.elems(0) + } + resVal.asInstanceOf[resTpe.Type] + } + + rewrite def tail: T = ??? + } object *: { diff --git a/library/src-scala3/scala/typelevel/package.scala b/library/src-scala3/scala/typelevel/package.scala new file mode 100644 index 000000000000..17220ab3187c --- /dev/null +++ b/library/src-scala3/scala/typelevel/package.scala @@ -0,0 +1,6 @@ +package scala + +package object typelevel { + erased def erasedValue[T]: T = ??? + case class Typed[T](val value: T) { type Type = T } +} \ No newline at end of file diff --git a/library/src/scala/TupleXXL.scala b/library/src/scala/TupleXXL.scala new file mode 100644 index 000000000000..991863f7140e --- /dev/null +++ b/library/src/scala/TupleXXL.scala @@ -0,0 +1,14 @@ +package scala + +final class TupleXXL private (es: Array[Object]) { + override def toString = elems.mkString("(", ",", ")") + override def hashCode = getClass.hashCode * 41 + elems.deep.hashCode + override def equals(that: Any) = that match { + case that: TupleXXL => this.elems.deep.equals(that.elems.deep) + case _ => false + } + def elems: Array[Object] = es +} +object TupleXXL { + def apply(elems: Array[Object]) = new TupleXXL(elems.clone) +} diff --git a/tests/run/Tuple.scala b/tests/run/Tuple.scala index 84a540b617e0..72e6f000286f 100644 --- a/tests/run/Tuple.scala +++ b/tests/run/Tuple.scala @@ -75,7 +75,6 @@ object Tuple { val e4c: Int = conc1(1) val e5c: Int = conc2(0) val e6c: Double = conc2(4) - } object Test extends App \ No newline at end of file diff --git a/tests/run/tuples1.check b/tests/run/tuples1.check new file mode 100644 index 000000000000..b9f0e38fac20 --- /dev/null +++ b/tests/run/tuples1.check @@ -0,0 +1,13 @@ +() +(1) +(A,1) +(2,A,1) +(B,2,A,1) +(3,B,2,A,1) +(C,3,B,2,A,1) +(4,C,3,B,2,A,1) +(D,4,C,3,B,2,A,1) +h1 = 1 +h2 = A +h7 = 4 +h8 = D diff --git a/tests/run/tuples1.scala b/tests/run/tuples1.scala new file mode 100644 index 000000000000..6b78a81e77a5 --- /dev/null +++ b/tests/run/tuples1.scala @@ -0,0 +1,37 @@ +object Test extends App { + val x0 = (); println(x0) + val x1 = 1 *: x0; println(x1) + val x2 = "A" *: x1; println(x2) + val x3 = 2 *: x2; println(x3) + val x4 = "B" *: x3; println(x4) + val x5 = 3 *: x4; println(x5) + val x6 = "C" *: x5; println(x6) + val x7 = 4 *: x6; println(x7) + val x8 = "D" *: x7; println(x8) + val h1 = x1.head; val h1c: Int = h1; println(s"h1 = $h1") + val h2 = x2.head; val h2c: String = h2; println(s"h2 = $h2") + val h7 = x7.head; val h7c: Int = h7; println(s"h7 = $h7") + val h8 = x8.head; val h8c: String = h8; println(s"h8 = $h8") +/* + val t1 = x1.tail; val t1c: Unit = t1; println(s"t1 = $t1") + val t2 = x2.tail; val t2c: Int *: Unit = t2; println(s"t2 = $t2") + val t7 = x7.tail; val t7c: String *: Int *: Unit = t7.tail.tail.tail.tail; println(s"t7 = $t7") + val t8 = x8.tail; val t8c: Int = t8(6); println(s"t8 = $t8") + val a1_0 = x1(0); val a1_0c: Int = a1_0; println(s"a1_0 = $a1_0") + val a2_0 = x2(0); val a2_0c: String = a2_0; println(s"a2_0 = $a2_0") + val a3_1 = x3(1); val a3_1c: String = a3_1; println(s"a3_1 = $a3_1") + val a4_3 = x4(3); val a4_3c: Int = a4_3; println(s"a4_3 = $a4_3") + val a6_4 = x6(4); val a6_4c: String = a6_4; println(s"a6_4 = $a6_4") + val a8_0 = x8(0); val a8_0c: String = a8_0; println(s"a8_0 = $a8_0") + val c0_0 = x0 ++ x0; val c0_0c: Unit = c0_0; println(s"c0_0 = $c0_0") + val c0_1 = x0 ++ x1; val c0_1c: Int *: Unit = c0_1c; println(s"c0_1 = $c0_1") + val c1_0 = x1 ++ x0; val c1_0c: Int *: Unit = c1_0c; println(s"c1_0 = $c1_0") + val c0_4 = x0 ++ x4; val c0_4c: String *: Int *: String *: Int *: Unit = c0_4; println(s"c0_4 = $c0_4") + val c4_0 = x4 ++ x0; val c4_0c: String *: Int *: String *: Int *: Unit = c4_0; println(s"c4_0 = $c4_0") + val c1_1 = x1 ++ x1; val c1_1c: Int *: Int *: Unit = c1_1; println(s"c1_1 = $c1_1") + val c1_8 = x1 ++ x8; val c1_8c: Int *: String *: Int *: String *: Int *: String *: Int *: String *: Int *: Unit = c1_8; println(s"c1_8 = $c1_8") + val c2_1 = x2 ++ x1; val c2_1c: String *: Int *: Int *: Unit = c2_1; println(s"c2_1 = $c2_1") + val c2_2 = x2 ++ x2; val c2_2c: String *: Int *: String *: Int *: Unit = c2_2; println(s"c2_2 = $c2_2") + val c2_3 = x2 ++ x3; val c2_3c: String *: Int *: Int *: String *: Int *: Unit = c2_3; println(s"c2_3 = $c2_3") + val c3_3 = x3 ++ x3; val c3_3c: Int *: String *: Int *: Int *: String *: Int *: Unit = c3_3; println(s"c3_3 = $c3_3")*/ +} From 0a24170c5160d20d24935ead7b1da00c5733ed29 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 14 Aug 2018 16:11:41 +0200 Subject: [PATCH 06/17] Fix TupleXXL equals Does not rely anymore on `deep`, which is dropped in Scala 2.14. --- library/src/scala/TupleXXL.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/src/scala/TupleXXL.scala b/library/src/scala/TupleXXL.scala index 991863f7140e..08bd4350d8b6 100644 --- a/library/src/scala/TupleXXL.scala +++ b/library/src/scala/TupleXXL.scala @@ -1,10 +1,11 @@ package scala +import java.util.Arrays.deepEquals final class TupleXXL private (es: Array[Object]) { override def toString = elems.mkString("(", ",", ")") override def hashCode = getClass.hashCode * 41 + elems.deep.hashCode override def equals(that: Any) = that match { - case that: TupleXXL => this.elems.deep.equals(that.elems.deep) + case that: TupleXXL => deepEquals(this.elems, that.elems) case _ => false } def elems: Array[Object] = es From b9f3ab753f7cb6fc74da6854a46c87d8ff3d21a5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 14 Aug 2018 16:12:35 +0200 Subject: [PATCH 07/17] More generative operations on Tuples: tail, apply, ++ --- library/src-scala3/scala/Tuple.scala | 111 ++++++++++++++++++++++++++- tests/run/tuples1.check | 21 +++++ tests/run/tuples1.scala | 3 +- 3 files changed, 132 insertions(+), 3 deletions(-) diff --git a/library/src-scala3/scala/Tuple.scala b/library/src-scala3/scala/Tuple.scala index ae4e30c6dbe7..f77f08fc0289 100644 --- a/library/src-scala3/scala/Tuple.scala +++ b/library/src-scala3/scala/Tuple.scala @@ -45,6 +45,46 @@ sealed trait Tuple extends Any { fromArray[resTpe.Type]($consArray(x, toArray)) } } + + rewrite def ++(that: Tuple): Tuple = { + erased val resTpe = Typed(_concat(this, that)) + rewrite _size(this) match { + case 0 => + that + case 1 => + if (_size(that) == 0) this + else (asInstanceOf[Tuple1[_]]._1 *: that).asInstanceOf[resTpe.Type] + case 2 => + val t = asInstanceOf[Tuple2[_, _]] + rewrite _size(that) match { + case 0 => this + case 1 => + val u = that.asInstanceOf[Tuple1[_]] + Tuple3(t._1, t._2, u._1).asInstanceOf[resTpe.Type] + case 2 => + val u = that.asInstanceOf[Tuple2[_, _]] + Tuple4(t._1, t._2, u._1, u._2).asInstanceOf[resTpe.Type] + case _ => + genericConcat[resTpe.Type](this, that) + } + case 3 => + val t = asInstanceOf[Tuple3[_, _, _]] + rewrite _size(that) match { + case 0 => this + case 1 => + val u = that.asInstanceOf[Tuple1[_]] + Tuple4(t._1, t._2, t._3, u._1).asInstanceOf[resTpe.Type] + case _ => + genericConcat[resTpe.Type](this, that) + } + case _ => + if (_size(that) == 0) this + else genericConcat[resTpe.Type](this, that) + } + } + + rewrite def genericConcat[T <: Tuple](xs: Tuple, ys: Tuple): Tuple = + fromArray[T](xs.toArray ++ ys.toArray) } object Tuple { @@ -83,6 +123,20 @@ object Tuple { case _: (x *: _) => erasedValue[x] } + private[scala] rewrite def _tail(xs: Tuple): Tuple = rewrite xs match { + case _: (_ *: xs1) => erasedValue[xs1] + } + + private[scala] rewrite def _index(xs: Tuple, n: Int): Any = rewrite xs match { + case _: (x *: _) if n == 0 => erasedValue[x] + case _: (_ *: xs1) if n > 0 => _index(erasedValue[xs1], n - 1) + } + + private[scala] rewrite def _concat(xs: Tuple, ys: Tuple): Tuple = rewrite xs match { + case _: Unit => ys + case _: (x1 *: xs1) => _pair(erasedValue[x1], _concat(erasedValue[xs1], ys)) + } + rewrite def fromArray[T <: Tuple](xs: Array[Object]): T = rewrite _size(erasedValue[T]) match { case 0 => ().asInstanceOf[T] @@ -140,8 +194,63 @@ sealed class *:[+H, +T <: Tuple] extends Tuple { resVal.asInstanceOf[resTpe.Type] } - rewrite def tail: T = ??? + rewrite def tail: Tuple = { + erased val resTpe = Typed(_tail(this)) + rewrite _size(this) match { + case 1 => + () + case 2 => + val t = asInstanceOf[Tuple2[_, _]] + Tuple1(t._2).asInstanceOf[resTpe.Type] + case 3 => + val t = asInstanceOf[Tuple3[_, _, _]] + Tuple2(t._2, t._3).asInstanceOf[resTpe.Type] + case 4 => + val t = asInstanceOf[Tuple4[_, _, _, _]] + Tuple3(t._2, t._3, t._4).asInstanceOf[resTpe.Type] + case 5 => + val t = asInstanceOf[Tuple5[_, _, _, _, _]] + Tuple4(t._2, t._3, t._4, t._5).asInstanceOf[resTpe.Type] + case n if n > 5 => + fromArray[resTpe.Type](toArray.tail) + } + } + rewrite def apply(n: Int): Any = { + erased val resTpe = Typed(_index(this, n)) + rewrite _size(this) match { + case 1 => + val t = asInstanceOf[Tuple1[_]] + rewrite n match { + case 0 => t._1.asInstanceOf[resTpe.Type] + } + case 2 => + val t = asInstanceOf[Tuple2[_, _]] + rewrite n match { + case 0 => t._1.asInstanceOf[resTpe.Type] + case 1 => t._2.asInstanceOf[resTpe.Type] + } + case 3 => + val t = asInstanceOf[Tuple3[_, _, _]] + rewrite n match { + case 0 => t._1.asInstanceOf[resTpe.Type] + case 1 => t._2.asInstanceOf[resTpe.Type] + case 2 => t._3.asInstanceOf[resTpe.Type] + } + case 4 => + val t = asInstanceOf[Tuple4[_, _, _, _]] + rewrite n match { + case 0 => t._1.asInstanceOf[resTpe.Type] + case 1 => t._2.asInstanceOf[resTpe.Type] + case 2 => t._3.asInstanceOf[resTpe.Type] + case 3 => t._4.asInstanceOf[resTpe.Type] + } + case s if s > 4 && s <= $MaxSpecialized && n >= 0 && n < s => + asInstanceOf[Product].productElement(n).asInstanceOf[resTpe.Type] + case s if s > $MaxSpecialized && n >= 0 && n < s => + asInstanceOf[TupleXXL].elems(n).asInstanceOf[resTpe.Type] + } + } } object *: { diff --git a/tests/run/tuples1.check b/tests/run/tuples1.check index b9f0e38fac20..6957f0548302 100644 --- a/tests/run/tuples1.check +++ b/tests/run/tuples1.check @@ -11,3 +11,24 @@ h1 = 1 h2 = A h7 = 4 h8 = D +t1 = () +t2 = (1) +t7 = (C,3,B,2,A,1) +t8 = (4,C,3,B,2,A,1) +a1_0 = 1 +a2_0 = A +a3_1 = A +a4_3 = 1 +a6_4 = A +a8_0 = D +c0_0 = () +c0_1 = (1) +c1_0 = (1) +c0_4 = (B,2,A,1) +c4_0 = (B,2,A,1) +c1_1 = (1,1) +c1_8 = (1,D,4,C,3,B,2,A,1) +c2_1 = (A,1,1) +c2_2 = (A,1,A,1) +c2_3 = (A,1,2,A,1) +c3_3 = (2,A,1,2,A,1) diff --git a/tests/run/tuples1.scala b/tests/run/tuples1.scala index 6b78a81e77a5..800dc2149562 100644 --- a/tests/run/tuples1.scala +++ b/tests/run/tuples1.scala @@ -12,7 +12,6 @@ object Test extends App { val h2 = x2.head; val h2c: String = h2; println(s"h2 = $h2") val h7 = x7.head; val h7c: Int = h7; println(s"h7 = $h7") val h8 = x8.head; val h8c: String = h8; println(s"h8 = $h8") -/* val t1 = x1.tail; val t1c: Unit = t1; println(s"t1 = $t1") val t2 = x2.tail; val t2c: Int *: Unit = t2; println(s"t2 = $t2") val t7 = x7.tail; val t7c: String *: Int *: Unit = t7.tail.tail.tail.tail; println(s"t7 = $t7") @@ -33,5 +32,5 @@ object Test extends App { val c2_1 = x2 ++ x1; val c2_1c: String *: Int *: Int *: Unit = c2_1; println(s"c2_1 = $c2_1") val c2_2 = x2 ++ x2; val c2_2c: String *: Int *: String *: Int *: Unit = c2_2; println(s"c2_2 = $c2_2") val c2_3 = x2 ++ x3; val c2_3c: String *: Int *: Int *: String *: Int *: Unit = c2_3; println(s"c2_3 = $c2_3") - val c3_3 = x3 ++ x3; val c3_3c: Int *: String *: Int *: Int *: String *: Int *: Unit = c3_3; println(s"c3_3 = $c3_3")*/ + val c3_3 = x3 ++ x3; val c3_3c: Int *: String *: Int *: Int *: String *: Int *: Unit = c3_3; println(s"c3_3 = $c3_3") } From 26f0d7506963dde1abddfebe2aae9791063b587e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 14 Aug 2018 17:56:12 +0200 Subject: [PATCH 08/17] Erase *: to tuple classes Was erased to Object before, but this loses precision and breaks binary compatibility with Scala 2. --- .../dotty/tools/dotc/core/Definitions.scala | 4 ++- .../dotty/tools/dotc/core/TypeErasure.scala | 32 ++++++++++++++++--- library/src/scala/TupleXXL.scala | 4 +-- tests/run/tuples1.scala | 2 +- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index daf063fc3460..cddb71e90fbf 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -711,6 +711,8 @@ class Definitions { lazy val PairType = ctx.requiredClassRef("scala.*:") def PairClass(implicit ctx: Context) = PairType.symbol.asClass + lazy val TupleXXLType = ctx.requiredClassRef("scala.TupleXXL") + def TupleXXLClass(implicit ctx: Context) = TupleXXLType.symbol.asClass // Annotation base classes lazy val AnnotationType = ctx.requiredClassRef("scala.annotation.Annotation") @@ -1203,7 +1205,7 @@ class Definitions { def isValueSubClass(sym1: Symbol, sym2: Symbol) = valueTypeEnc(sym2.asClass.name) % valueTypeEnc(sym1.asClass.name) == 0 - lazy val erasedToObject = Set[Symbol](AnyClass, AnyValClass, TupleClass, PairClass, SingletonClass) + lazy val erasedToObject = Set[Symbol](AnyClass, AnyValClass, TupleClass, SingletonClass) // ----- Initialization --------------------------------------------------- diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 40186a1bedcb..1e55c339baeb 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -33,6 +33,9 @@ import scala.annotation.tailrec */ object TypeErasure { + private def erasureDependsOnArgs(tp: Type)(implicit ctx: Context) = + tp.isRef(defn.ArrayClass) || tp.isRef(defn.PairClass) + /** A predicate that tests whether a type is a legal erased type. Only asInstanceOf and * isInstanceOf may have types that do not satisfy the predicate. * ErasedValueType is considered an erased type because it is valid after Erasure (it is @@ -44,7 +47,7 @@ object TypeErasure { case tp: TypeRef => val sym = tp.symbol sym.isClass && - sym != defn.ArrayClass && + !erasureDependsOnArgs(tp) && !defn.erasedToObject.contains(sym) && !defn.isSyntheticFunctionClass(sym) case _: TermRef => @@ -389,6 +392,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean else eraseNormalClassRef(tp) case tp: AppliedType => if (tp.tycon.isRef(defn.ArrayClass)) eraseArray(tp) + else if (tp.tycon.isRef(defn.PairClass)) erasePair(tp) else if (tp.isRepeatedParam) apply(tp.underlyingIfRepeated(isJava)) else apply(tp.superType) case _: TermRef | _: ThisType => @@ -419,9 +423,13 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean case tp @ ClassInfo(pre, cls, parents, decls, _) => if (cls is Package) tp else { + def eraseParent(tp: Type) = tp.dealias match { + case tp: AppliedType if tp.tycon.isRef(defn.PairClass) => defn.ObjectType + case _ => apply(tp) + } val erasedParents: List[Type] = if ((cls eq defn.ObjectClass) || cls.isPrimitiveValueClass) Nil - else parents.mapConserve(apply) match { + else parents.mapConserve(eraseParent) match { case tr :: trs1 => assert(!tr.classSymbol.is(Trait), cls) val tr1 = if (cls is Trait) defn.ObjectType else tr @@ -449,6 +457,22 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean else JavaArrayType(arrayErasure(elemtp)) } + private def erasePair(tp: Type)(implicit ctx: Context): Type = { + def tupleArity(tp: Type): Int = tp.dealias match { + case AppliedType(tycon, _ :: tl :: Nil) if tycon.isRef(defn.PairClass) => + val arity = tupleArity(tl) + if (arity < 0) arity else arity + 1 + case tp1 => + if (tp1.isRef(defn.UnitClass)) 0 + else if (defn.isTupleClass(tp1.classSymbol)) tp1.dealias.argInfos.length + else -1 + } + val arity = tupleArity(tp) + if (arity < 0) defn.ObjectType + else if (arity <= Definitions.MaxTupleArity) defn.TupleType(arity) + else defn.TupleXXLType + } + /** The erasure of a symbol's info. This is different from `apply` in the way `ExprType`s and * `PolyType`s are treated. `eraseInfo` maps them them to method types, whereas `apply` maps them * to the underlying type. @@ -491,7 +515,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 AppliedType(tycon, _) if !(tycon isRef defn.ArrayClass) => + case AppliedType(tycon, _) if !erasureDependsOnArgs(tycon) => eraseResult(tycon) case _ => this(tp) @@ -533,7 +557,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean normalizeClass(sym.asClass).fullName.asTypeName case tp: AppliedType => sigName( - if (tp.tycon.isRef(defn.ArrayClass)) this(tp) + if (erasureDependsOnArgs(tp.tycon)) this(tp) else if (tp.tycon.typeSymbol.isClass) tp.underlying else tp.superType) case ErasedValueType(_, underlying) => diff --git a/library/src/scala/TupleXXL.scala b/library/src/scala/TupleXXL.scala index 08bd4350d8b6..c473c22bd494 100644 --- a/library/src/scala/TupleXXL.scala +++ b/library/src/scala/TupleXXL.scala @@ -1,9 +1,9 @@ package scala -import java.util.Arrays.deepEquals +import java.util.Arrays.{deepEquals, deepHashCode} final class TupleXXL private (es: Array[Object]) { override def toString = elems.mkString("(", ",", ")") - override def hashCode = getClass.hashCode * 41 + elems.deep.hashCode + override def hashCode = getClass.hashCode * 41 + deepHashCode(elems) override def equals(that: Any) = that match { case that: TupleXXL => deepEquals(this.elems, that.elems) case _ => false diff --git a/tests/run/tuples1.scala b/tests/run/tuples1.scala index 800dc2149562..ad310f616b8e 100644 --- a/tests/run/tuples1.scala +++ b/tests/run/tuples1.scala @@ -1,7 +1,7 @@ object Test extends App { val x0 = (); println(x0) val x1 = 1 *: x0; println(x1) - val x2 = "A" *: x1; println(x2) + val x2 = ("A", 1); println(x2) val x3 = 2 *: x2; println(x3) val x4 = "B" *: x3; println(x4) val x5 = 3 *: x4; println(x5) From 2d1427d5aac894bcbc36ef043eb6545ef11c1e3a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 15 Aug 2018 18:09:19 +0200 Subject: [PATCH 09/17] Allow tuple literals to extend beyond 22 --- .../src/dotty/tools/dotc/ast/Desugar.scala | 27 +++++---- .../tools/dotc/core/ConstraintHandling.scala | 56 ++++++++----------- .../dotty/tools/dotc/core/Definitions.scala | 4 ++ .../dotty/tools/dotc/core/TypeErasure.scala | 12 +--- .../tools/dotc/core/tasty/TastyFormat.scala | 3 + .../tools/dotc/core/tasty/TreePickler.scala | 3 + .../tools/dotc/core/tasty/TreeUnpickler.scala | 2 + .../dotc/reporting/diagnostic/messages.scala | 16 ------ .../tools/dotc/transform/TypeTestsCasts.scala | 8 ++- .../tools/dotc/transform/TypeUtils.scala | 23 ++++++++ .../src/dotty/tools/dotc/typer/Typer.scala | 33 +++++++++++ library/src-scala3/scala/Tuple.scala | 2 +- library/src/scala/TupleXXL.scala | 2 + tests/neg/tuple-patterns.scala | 8 +++ tests/run/tuple-patterns.check | 9 +++ tests/run/tuple-patterns.scala | 40 +++++++++++++ tests/run/tuples1.check | 1 + tests/run/tuples1.scala | 11 ++++ 18 files changed, 189 insertions(+), 71 deletions(-) create mode 100644 tests/neg/tuple-patterns.scala create mode 100644 tests/run/tuple-patterns.check create mode 100644 tests/run/tuple-patterns.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 3b27999cad35..1104ecb9230e 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -815,6 +815,23 @@ object desugar { makeOp(right, left, Position(op.pos.start, right.pos.end)) } + /** Translate tuple expressions of arity <= 22 + * + * () ==> () + * (t) ==> t + * (t1, ..., tN) ==> TupleN(t1, ..., tN) + */ + def smallTuple(tree: Tuple)(implicit ctx: Context): Tree = { + val ts = tree.trees + val arity = ts.length + assert(arity <= Definitions.MaxTupleArity) + def tupleTypeRef = defn.TupleType(arity) + if (arity == 1) ts.head + else if (ctx.mode is Mode.Type) AppliedTypeTree(ref(tupleTypeRef), ts) + else if (arity == 0) unitLiteral + else Apply(ref(tupleTypeRef.classSymbol.companionModule.termRef), ts) + } + /** Make closure corresponding to function. * params => body * ==> @@ -1141,16 +1158,6 @@ object desugar { case PrefixOp(op, t) => val nspace = if (ctx.mode.is(Mode.Type)) tpnme else nme Select(t, nspace.UNARY_PREFIX ++ op.name) - case Tuple(ts) => - val arity = ts.length - def tupleTypeRef = defn.TupleType(arity) - if (arity > Definitions.MaxTupleArity) { - ctx.error(TupleTooLong(ts), tree.pos) - unitLiteral - } else if (arity == 1) ts.head - else if (ctx.mode is Mode.Type) AppliedTypeTree(ref(tupleTypeRef), ts) - else if (arity == 0) unitLiteral - else Apply(ref(tupleTypeRef.classSymbol.companionModule.termRef), ts) case WhileDo(cond, body) => // {