Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #9482: simplified Manifest synthesis #13142

Merged
merged 3 commits into from
Jul 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
118 changes: 117 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -375,14 +375,130 @@ 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
Comment on lines +378 to +380
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this method could be used to solve #13131 :)


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

def canManifest(tp: Manifestable, topLevel: Boolean) =
val sym = tp.typeSymbol
!sym.isAbstractType
&& hasStableErasure(tp)
&& !(topLevel && defn.isBottomClassAfterErasure(sym))

/** 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 canManifest(tp, topLevel) =>
val sym = tp.typeSymbol
if sym.isPrimitiveValueClass || defn.SpecialManifestClasses.contains(sym) then
singletonManifest(sym.name.toTermName)
else
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 =>
val manifest = synthesize(fullyDefinedType(arg, "Manifest argument", span), kind, topLevel = true)
if manifest != EmptyTree then
report.deprecationWarning(
i"""Compiler synthesis of Manifest and OptManifest is deprecated, instead
|replace with the type `scala.reflect.ClassTag[$arg]`.
|Alternatively, consider using the new metaprogramming features of Scala 3,
|see https://docs.scala-lang.org/scala3/reference/metaprogramming.html""", ctx.source.atSpan(span))
manifest
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,
defn.CanEqualClass -> synthesizedCanEqual,
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
Expand Down
14 changes: 14 additions & 0 deletions tests/neg-custom-args/deprecation/manifest-summoning.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- Error: tests/neg-custom-args/deprecation/manifest-summoning.scala:1:34 ----------------------------------------------
1 |val foo = manifest[List[? <: Int]] // error
| ^
| Compiler synthesis of Manifest and OptManifest is deprecated, instead
| replace with the type `scala.reflect.ClassTag[List[? <: Int]]`.
| Alternatively, consider using the new metaprogramming features of Scala 3,
| see https://docs.scala-lang.org/scala3/reference/metaprogramming.html
-- Error: tests/neg-custom-args/deprecation/manifest-summoning.scala:2:41 ----------------------------------------------
2 |val bar = optManifest[Array[? <: String]] // error
| ^
| Compiler synthesis of Manifest and OptManifest is deprecated, instead
| replace with the type `scala.reflect.ClassTag[Array[? <: String]]`.
| Alternatively, consider using the new metaprogramming features of Scala 3,
| see https://docs.scala-lang.org/scala3/reference/metaprogramming.html
2 changes: 2 additions & 0 deletions tests/neg-custom-args/deprecation/manifest-summoning.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
val foo = manifest[List[? <: Int]] // error
val bar = optManifest[Array[? <: String]] // error
27 changes: 27 additions & 0 deletions tests/neg/manifest-summoning.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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
}

object Foo {
type F[T] <: T
manifest[Array[F[Int]]] // error
}

object opaques {
opaque type OpaqueList[+A] = List[A]
}
import opaques.*

val `OpaqueList[Int]` = manifest[OpaqueList[Int]] // error (opaque types are not supported)
11 changes: 11 additions & 0 deletions tests/pos/i9482.scala
Original file line number Diff line number Diff line change
@@ -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
}
22 changes: 22 additions & 0 deletions tests/pos/manifest-summoning.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
object Foo {

object opaques {
opaque type Inner = String
val i: Inner = "i"
}

val singleton: opaques.Inner = opaques.i

val om_Inner = optManifest[opaques.Inner] // NoManifest
val om_singleton = optManifest[singleton.type] // NoManifest
val ct_Inner = reflect.classTag[opaques.Inner]
val ct_singleton = reflect.classTag[singleton.type]
}

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] }
83 changes: 83 additions & 0 deletions tests/run/i9482.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import scala.reflect.{OptManifest, ClassTag}

object Ref {

object Sentinel

def makeWithArr[A: OptManifest]: String = optManifest[A] match {
case m: ClassTag[_] => m.newArray(0).asInstanceOf[AnyRef] match {
// these can be reordered, so long as Unit comes before AnyRef
case _: Array[Boolean] => "bool"
case _: Array[Byte] => "byte"
case _: Array[Short] => "short"
case _: Array[Char] => "char"
case _: Array[Int] => "int"
case _: Array[Float] => "float"
case _: Array[Long] => "long"
case _: Array[Double] => "double"
case _: Array[Unit] => "unit"
case a: Array[AnyRef] => a.getClass.getComponentType.getName
}
case _ => "<?>"
}

def make[A: OptManifest]: String = optManifest[A] match {
case m: ClassTag[a] => m match {
case ClassTag.Boolean => "bool"
case ClassTag.Byte => "byte"
case ClassTag.Short => "short"
case ClassTag.Char => "char"
case ClassTag.Int => "int"
case ClassTag.Float => "float"
case ClassTag.Long => "long"
case ClassTag.Double => "double"
case ClassTag.Unit => "unit"
case ClassTag.Any => "any"
case ClassTag.AnyVal => "anyval"
case ClassTag.Object => "anyref"
case _ => m.runtimeClass.getName
}
case NoManifest => "<?>"
}

}

import Ref.*

def baz[A] = Ref.makeWithArr[A]
def qux[A] = Ref.make[A]

@main def Test = {

assert(Ref.makeWithArr[Boolean] == "bool")
assert(Ref.makeWithArr[Byte] == "byte")
assert(Ref.makeWithArr[Short] == "short")
assert(Ref.makeWithArr[Char] == "char")
assert(Ref.makeWithArr[Int] == "int")
assert(Ref.makeWithArr[Float] == "float")
assert(Ref.makeWithArr[Long] == "long")
assert(Ref.makeWithArr[Double] == "double")
assert(Ref.makeWithArr[Unit] == "unit")
assert(Ref.makeWithArr["abc"] == "java.lang.String")
assert(Ref.makeWithArr[Null] == "<?>")
assert(Ref.makeWithArr[Nothing] == "<?>")
assert(baz[Int] == "<?>")

assert(Ref.make[Boolean] == "bool")
assert(Ref.make[Byte] == "byte")
assert(Ref.make[Short] == "short")
assert(Ref.make[Char] == "char")
assert(Ref.make[Int] == "int")
assert(Ref.make[Float] == "float")
assert(Ref.make[Long] == "long")
assert(Ref.make[Double] == "double")
assert(Ref.make[Unit] == "unit")
assert(Ref.make[Any] == "any")
assert(Ref.make[AnyVal] == "anyval")
assert(Ref.make[AnyRef] == "anyref")
assert(Ref.make["abc"] == "java.lang.String")
assert(Ref.make[Null] == "<?>")
assert(Ref.make[Nothing] == "<?>")
assert(qux[Int] == "<?>")

}
Loading