From 4ab2fefa643639e07011f27419b60a3838ab0c5b Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Tue, 13 Jul 2021 17:52:59 +0200 Subject: [PATCH] fix #9482: implement manifest algorithm --- .../dotty/tools/dotc/core/Definitions.scala | 8 ++ .../src/dotty/tools/dotc/core/StdNames.scala | 5 +- .../dotty/tools/dotc/typer/Synthesizer.scala | 111 ++++++++++++++++- tests/neg/manifest-summoning.scala | 15 +++ tests/pos/i9482.scala | 11 ++ tests/pos/manifest-summoning.scala | 29 +++++ tests/run/manifest-summoning.scala | 114 ++++++++++++++++++ 7 files changed, 291 insertions(+), 2 deletions(-) create mode 100644 tests/neg/manifest-summoning.scala create mode 100644 tests/pos/i9482.scala create mode 100644 tests/pos/manifest-summoning.scala create mode 100644 tests/run/manifest-summoning.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index a0db1a893062..7c1c2494d323 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -764,6 +764,12 @@ class Definitions { @tu lazy val SelectableClass: ClassSymbol = requiredClass("scala.Selectable") @tu lazy val WithoutPreciseParameterTypesClass: Symbol = requiredClass("scala.Selectable.WithoutPreciseParameterTypes") + @tu lazy val ManifestClass: ClassSymbol = requiredClass("scala.reflect.Manifest") + @tu lazy val ManifestFactoryModule: Symbol = requiredModule("scala.reflect.ManifestFactory") + @tu lazy val ClassManifestFactoryModule: Symbol = requiredModule("scala.reflect.ClassManifestFactory") + @tu lazy val OptManifestClass: ClassSymbol = requiredClass("scala.reflect.OptManifest") + @tu lazy val NoManifestModule: Symbol = requiredModule("scala.reflect.NoManifest") + @tu lazy val ReflectPackageClass: Symbol = requiredPackage("scala.reflect.package").moduleClass @tu lazy val ClassTagClass: ClassSymbol = requiredClass("scala.reflect.ClassTag") @tu lazy val ClassTagModule: Symbol = ClassTagClass.companionModule @@ -1433,6 +1439,8 @@ class Definitions { @tu lazy val SpecialClassTagClasses: Set[Symbol] = Set(UnitClass, AnyClass, AnyValClass) + @tu lazy val SpecialManifestClasses: Set[Symbol] = Set(AnyClass, AnyValClass, ObjectClass, NullClass, NothingClass) + /** Classes that are known not to have an initializer irrespective of * whether NoInits is set. Note: FunctionXXLClass is in this set * because if it is compiled by Scala2, it does not get a NoInit flag. diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 5c718d4af0da..b6aea21bee8b 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -367,7 +367,6 @@ object StdNames { val EnumValue: N = "EnumValue" val ExistentialTypeTree: N = "ExistentialTypeTree" val Flag : N = "Flag" - val floatHash: N = "floatHash" val Ident: N = "Ident" val Import: N = "Import" val Literal: N = "Literal" @@ -414,6 +413,7 @@ object StdNames { val argv : N = "argv" val arrayClass: N = "arrayClass" val arrayElementClass: N = "arrayElementClass" + val arrayType: N = "arrayType" val arrayValue: N = "arrayValue" val array_apply : N = "array_apply" val array_clone : N = "array_clone" @@ -440,6 +440,7 @@ object StdNames { val checkInitialized: N = "checkInitialized" val ClassManifestFactory: N = "ClassManifestFactory" val classOf: N = "classOf" + val classType: N = "classType" val clone_ : N = "clone" val common: N = "common" val compiletime : N = "compiletime" @@ -481,6 +482,7 @@ object StdNames { val find_ : N = "find" val flagsFromBits : N = "flagsFromBits" val flatMap: N = "flatMap" + val floatHash: N = "floatHash" val foreach: N = "foreach" val format: N = "format" val fromDigits: N = "fromDigits" @@ -626,6 +628,7 @@ object StdNames { val values: N = "values" val view_ : N = "view" val wait_ : N = "wait" + val wildcardType: N = "wildcardType" val withFilter: N = "withFilter" val withFilterIfRefutable: N = "withFilterIfRefutable$" val WorksheetWrapper: N = "WorksheetWrapper" diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index e848a19e147e..f584664100d1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -16,6 +16,8 @@ import transform.TypeUtils._ import transform.SyntheticMembers._ import util.Property import annotation.{tailrec, constructorOnly} +import collection.mutable +import dotty.tools.dotc.core.SymDenotations.LazyType /** Synthesize terms for special classes */ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): @@ -375,6 +377,110 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): synthesizedSumMirror(formal, span) case _ => EmptyTree + private def escapeJavaArray(elemTp: Type)(using Context): Type = elemTp match + case JavaArrayType(elemTp1) => defn.ArrayOf(escapeJavaArray(elemTp1)) + case _ => elemTp + + private enum ManifestKind: + case Full, Opt, Clss + + /** The kind that should be used for an array element, if we are `OptManifest` then this + * prevents wildcards arguments of Arrays being converted to `NoManifest` + */ + def arrayElem = if this == Full then this else Clss + + end ManifestKind + + /** Manifest factory that does enough to satisfy the equality semantics for + * - `scala.reflect.OptManifest` (only runtime class is recorded) + * - `scala.reflect.Manifest` (runtime class of arguments are recorded, with wildcard upper bounds wrapped) + * however,`toString` may be different. + * + * There are some differences to `ClassTag`, + * e.g. in Scala 2 `manifest[Int @unchecked]` will fail, but `classTag[Int @unchecked]` succeeds. + */ + private def manifestFactoryOf(kind: ManifestKind): SpecialHandler = (formal, span) => + import ManifestKind.* + + /* Creates a tree that calls the factory method called constructor in object scala.reflect.Manifest */ + def factoryManifest(constructor: TermName, tparg: Type, args: Tree*): Tree = + if args.contains(EmptyTree) then + EmptyTree + else + val factory = if kind == Full then defn.ManifestFactoryModule else defn.ClassManifestFactoryModule + applyOverloaded(ref(factory), constructor, args.toList, tparg :: Nil, Types.WildcardType).withSpan(span) + + /* Creates a tree representing one of the singleton manifests.*/ + def singletonManifest(name: TermName) = + ref(defn.ManifestFactoryModule).select(name).ensureApplied.withSpan(span) + + def synthArrayManifest(elemTp: Type, kind: ManifestKind, topLevel: Boolean): Tree = + factoryManifest(nme.arrayType, elemTp, synthesize(elemTp, kind.arrayElem, topLevel)) + + /** manifests generated from wildcards can not equal Int,Long,Any,AnyRef,AnyVal etc, + * so we wrap their upper bound. + */ + def synthWildcardManifest(tp: Manifestable, hi: Type, topLevel: Boolean): Tree = + factoryManifest(nme.wildcardType, tp, singletonManifest(nme.Nothing), synthesize(hi, Full, topLevel)) + + /** `Nil` if not full manifest */ + def synthArgManifests(tp: Manifestable): List[Tree] = tp match + case AppliedType(_, args) if kind == Full && tp.typeSymbol.isClass => + args.map(synthesize(_, Full, topLevel = false)) + case _ => + Nil + + /** This type contains all top-level types supported by Scala 2's algorithm */ + type Manifestable = + ThisType | TermRef | ConstantType | TypeRef | AppliedType | TypeBounds | RecType | RefinedType | AndType + + /** adapted from `syntheticClassTag` */ + def synthManifest(tp: Manifestable, kind: ManifestKind, topLevel: Boolean) = tp match + case defn.ArrayOf(elemTp) => synthArrayManifest(elemTp, kind, topLevel) + case TypeBounds(_, hi) if kind == Full => synthWildcardManifest(tp, hi, topLevel) + + case tp if hasStableErasure(tp) && !(topLevel && defn.isBottomClassAfterErasure(tp.typeSymbol)) => + val sym = + val sym0 = tp.typeSymbol + if sym0.isOpaqueAlias then sym0.opaqueAlias.typeSymbol + else sym0 + if sym.isPrimitiveValueClass || defn.SpecialManifestClasses.contains(sym) then + singletonManifest(sym.name.toTermName) + else + // should this be Scala 2 erasure? (e.g. intersection types behave differently) + erasure(tp) match + case JavaArrayType(elemTp) => + synthArrayManifest(escapeJavaArray(elemTp), kind, topLevel) + + case etp => + val clsArg = clsOf(etp).asInstance(defn.ClassType(tp)) // cast needed to resolve overloading + factoryManifest(nme.classType, tp, (clsArg :: synthArgManifests(tp))*) + + case _ => + EmptyTree + + end synthManifest + + def manifestOfType(tp0: Type, kind: ManifestKind, topLevel: Boolean): Tree = tp0.dealiasKeepAnnots match + case tp1: Manifestable => synthManifest(tp1, kind, topLevel) + case tp1 => EmptyTree + + def synthesize(tp: Type, kind: ManifestKind, topLevel: Boolean): Tree = + manifestOfType(tp, kind, topLevel) match + case EmptyTree if kind == Opt => ref(defn.NoManifestModule) + case result => result + + formal.argInfos match + case arg :: Nil => + synthesize(fullyDefinedType(arg, "Manifest argument", span), kind, topLevel = true) + case _ => + EmptyTree + + end manifestFactoryOf + + val synthesizedManifest: SpecialHandler = manifestFactoryOf(ManifestKind.Full) + val synthesizedOptManifest: SpecialHandler = manifestFactoryOf(ManifestKind.Opt) + val specialHandlers = List( defn.ClassTagClass -> synthesizedClassTag, defn.TypeTestClass -> synthesizedTypeTest, @@ -382,7 +488,10 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): defn.ValueOfClass -> synthesizedValueOf, defn.Mirror_ProductClass -> synthesizedProductMirror, defn.Mirror_SumClass -> synthesizedSumMirror, - defn.MirrorClass -> synthesizedMirror) + defn.MirrorClass -> synthesizedMirror, + defn.ManifestClass -> synthesizedManifest, + defn.OptManifestClass -> synthesizedOptManifest, + ) def tryAll(formal: Type, span: Span)(using Context): Tree = def recur(handlers: SpecialHandlers): Tree = handlers match diff --git a/tests/neg/manifest-summoning.scala b/tests/neg/manifest-summoning.scala new file mode 100644 index 000000000000..4e163a0adc48 --- /dev/null +++ b/tests/neg/manifest-summoning.scala @@ -0,0 +1,15 @@ +val `Array[Nothing]` = manifest[Array[Nothing]] // error +val `Array[Null]` = manifest[Array[Null]] // error +val m_Nothing = manifest[Nothing] // error +val m_Null = manifest[Null] // error + +val `Array[? <: Nothing]` = manifest[Array[? <: Nothing]] // error +val `Array[? <: Null]` = manifest[Array[? <: Null]] // error + +val `Int @unchecked` = manifest[Int @unchecked] // error + +val `0 | 1` = manifest[0 | 1] // error + +class Box[T] { + val m = manifest[T] // error +} diff --git a/tests/pos/i9482.scala b/tests/pos/i9482.scala new file mode 100644 index 000000000000..6549539e49a7 --- /dev/null +++ b/tests/pos/i9482.scala @@ -0,0 +1,11 @@ +import scala.reflect.OptManifest + +object Ref { + def make[A: OptManifest]: Ref[A] = ??? +} +trait Ref[A] + +trait Foo[A] { + val bar = Ref.make[Int] + val baz: Ref[A] = Ref.make +} diff --git a/tests/pos/manifest-summoning.scala b/tests/pos/manifest-summoning.scala new file mode 100644 index 000000000000..60422f55f8b0 --- /dev/null +++ b/tests/pos/manifest-summoning.scala @@ -0,0 +1,29 @@ +object Foo { + + object opaques { + opaque type Inner = String + val i: Inner = "i" + } + + val singleton: opaques.Inner = opaques.i + + val m_Inner = manifest[opaques.Inner] // we can see the erasure of the opaque type + val m_singleton = manifest[singleton.type] + val om_Inner = optManifest[opaques.Inner] + val om_singleton = optManifest[singleton.type] + val ct_Inner = reflect.classTag[opaques.Inner] + val ct_singleton = reflect.classTag[singleton.type] +} + +object Bar { + type F[T] <: T + manifest[Array[F[Int]]] // would not work in Scala 2 +} + +val `List[Nothing]` = manifest[List[Nothing]] +val `List[Array[Nothing]]` = manifest[List[Array[Nothing]]] // ok when Nothing is not the argument of top-level array + +val `Array[Array[List[Int]]]` = manifest[Array[Array[List[Int]]]] + +trait Mixin[T <: Mixin[T]] { type Self = T } +class Baz extends Mixin[Baz] { val m = manifest[Self] } diff --git a/tests/run/manifest-summoning.scala b/tests/run/manifest-summoning.scala new file mode 100644 index 000000000000..ca57799bb025 --- /dev/null +++ b/tests/run/manifest-summoning.scala @@ -0,0 +1,114 @@ +import scala.reflect.{classTag, ClassTag, NoManifest} + +@main def Test: Unit = + + /* ====== no manifest available ====== */ + + locally { + noManifest[Array[? <: Int]] // available as a manifest + noManifest[Array[? <: String]] // available as a manifest + noManifest[Array[Nothing]] + noManifest[Array[Null]] + noManifest[Nothing] + noManifest[Null] + } + + /* ====== ClassTag and OptManifest have the same runtime class and same equality ======= */ + + locally { + interopOpt[List[Int]] + interopOpt[List[? <: Int]] + } + + /* ====== Test some OptManifest have the same runtime class and are equal ======= */ + + locally { + sameClassEqualOpt[List[Int], List[? <: Int]] // not equal for full manifests + sameClassEqualOpt[List[Int], List[String]] // not equal for full manifests + } + + /* ============================================================================= */ + // The following tests rely on <:< being correct, i.e. `equals` on Manifest // + // uses `<:<` underneath. // + /* ============================================================================= */ + + /* ====== Test some Manifest have the same runtime class and are equal ======= */ + + locally { + trait A + trait B {def b: Int} + trait C {def c: Int} + trait D {def d: Int} + class fooAnnot extends scala.annotation.StaticAnnotation + + type SomeRefinedType = + ((B {def b: 0} & C) & ((A @fooAnnot) & D {def d: 2})) {def c: 1} + + object opaques { + opaque type OpaqueList[+A] = List[A] + opaque type OpaqueMap[A] = Map[A, A] + } + import opaques.* + + sameClassEqualMan[Array[? <: String], Array[String]] + sameClassEqualMan[SomeRefinedType, A] + + // manifests of opaque types so far do not track type arguments - + // if we did care we would need to fetch the manifest for the fully + // dealiased opaque type, i.e. dereference opaque aliases to opaque aliases + // otherwise the arities do not fully match + sameClassEqualMan[OpaqueList[Int], OpaqueList[String]] // arities match, List could be applied correctly + sameClassEqualMan[OpaqueMap[Int], OpaqueMap[String]] // arities do not match so `Map` would not be fully applied + } + + + /* ====== Test some Manifest have the same runtime class but are not equal ======= */ + + locally { + sameClassNonEqualMan[List[Int], List[? <: Int]] + sameClassNonEqualMan[List[Int], List[String]] + } + + /* ====== Test that some Manifest have the same runtime class, are not equal, but are `<:<` ======= */ + + locally { + class A + class B extends A + + sameClassSub[List[Int], List[AnyVal]] + sameClassSub[List[Unit], List[AnyVal]] + sameClassSub[List[B], List[A]] + sameClassSub[Array[List[B]], Array[List[A]]] + } + +end Test + +def noManifest[A: OptManifest] = + assert(optManifest[A] eq NoManifest) + +def interopOpt[A: ClassTag: OptManifest] = + assert(classTag[A] == optManifest[A]) + optManifest[A] match + case optA: ClassTag[_] => + assert(classTag[A].runtimeClass == optA.runtimeClass) + +def sameClassEqualOpt[A: OptManifest, B: OptManifest] = + assert(optManifest[A] == optManifest[B]) + (optManifest[A], optManifest[B]) match + case (a: ClassTag[_], b: ClassTag[_]) => + assert(a.runtimeClass == b.runtimeClass) + +def sameClassMan[A: Manifest, B: Manifest] = + assert(manifest[A].runtimeClass == manifest[B].runtimeClass) + +def sameClassEqualMan[A: Manifest, B: Manifest] = + sameClassMan[A, B] + assert(manifest[A] == manifest[B]) + +def sameClassNonEqualMan[A: Manifest, B: Manifest] = + sameClassMan[A, B] + assert(manifest[A] != manifest[B]) + +def sameClassSub[A: Manifest, B: Manifest] = + sameClassNonEqualMan[A, B] + assert(manifest[A] <:< manifest[B])