Skip to content

Commit 9450855

Browse files
authored
Take care of all the missing outer pointer in Scala 2 library (#22640)
Now that SIP-52 was approved, we can use it to hide constructors that would have been generated by nsc but not dotc.
2 parents 5971674 + a6e4102 commit 9450855

File tree

3 files changed

+756
-12
lines changed

3 files changed

+756
-12
lines changed

project/Scala2LibraryBootstrappedMiMaFilters.scala

+12-12
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ object Scala2LibraryBootstrappedMiMaFilters {
1212
// Scala language features
1313
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.language.<clinit>"),
1414
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.language#experimental.<clinit>"),
15+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.util.Properties.<clinit>"),
1516
ProblemFilters.exclude[FinalClassProblem]("scala.language$experimental$"),
1617
ProblemFilters.exclude[FinalClassProblem]("scala.languageFeature$*$"),
1718

@@ -68,18 +69,17 @@ object Scala2LibraryBootstrappedMiMaFilters {
6869
ProblemFilters.exclude[FinalMethodProblem]("scala.io.Source.RelaxedPositioner"),
6970
ProblemFilters.exclude[MissingFieldProblem]("scala.collection.ArrayOps#ReverseIterator.xs"),
7071
ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.NonLocalReturnControl.value"),
71-
) ++
72-
Seq( // DirectMissingMethodProblem
73-
"scala.collection.LinearSeqIterator#LazyCell.this",
74-
"scala.collection.mutable.PriorityQueue#ResizableArrayAccess.this",
75-
"scala.concurrent.BatchingExecutor#AbstractBatch.this",
76-
"scala.concurrent.Channel#LinkedList.this",
77-
"scala.Enumeration#ValueOrdering.this",
78-
"scala.io.Source#RelaxedPosition.this",
79-
"scala.collection.IterableOnceOps#Maximized.this", // New in 2.13.11: private inner class
80-
"scala.util.Properties.<clinit>",
81-
"scala.util.Sorting.scala$util$Sorting$$mergeSort$default$5",
82-
).map(ProblemFilters.exclude[DirectMissingMethodProblem])
72+
73+
// Missing outer pointers in private classes (not a problem)
74+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.LinearSeqIterator#LazyCell.this"),
75+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.PriorityQueue#ResizableArrayAccess.this"),
76+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.concurrent.BatchingExecutor#AbstractBatch.this"),
77+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.concurrent.Channel#LinkedList.this"),
78+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.IterableOnceOps#Maximized.this"),
79+
80+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.util.Sorting.scala$util$Sorting$$mergeSort$default$5"),
81+
82+
)
8383
}
8484
)
8585

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
/*
2+
* Scala (https://www.scala-lang.org)
3+
*
4+
* Copyright EPFL and Lightbend, Inc. dba Akka
5+
*
6+
* Licensed under Apache License 2.0
7+
* (http://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package scala
14+
15+
import scala.collection.{SpecificIterableFactory, StrictOptimizedIterableOps, View, immutable, mutable}
16+
import java.lang.reflect.{Field => JField, Method => JMethod}
17+
18+
import scala.annotation.{implicitNotFound, tailrec}
19+
import scala.reflect.NameTransformer._
20+
import scala.util.matching.Regex
21+
22+
/** Defines a finite set of values specific to the enumeration. Typically
23+
* these values enumerate all possible forms something can take and provide
24+
* a lightweight alternative to case classes.
25+
*
26+
* Each call to a `Value` method adds a new unique value to the enumeration.
27+
* To be accessible, these values are usually defined as `val` members of
28+
* the enumeration.
29+
*
30+
* All values in an enumeration share a common, unique type defined as the
31+
* `Value` type member of the enumeration (`Value` selected on the stable
32+
* identifier path of the enumeration instance).
33+
*
34+
* Values SHOULD NOT be added to an enumeration after its construction;
35+
* doing so makes the enumeration thread-unsafe. If values are added to an
36+
* enumeration from multiple threads (in a non-synchronized fashion) after
37+
* construction, the behavior of the enumeration is undefined.
38+
*
39+
* @example {{{
40+
* // Define a new enumeration with a type alias and work with the full set of enumerated values
41+
* object WeekDay extends Enumeration {
42+
* type WeekDay = Value
43+
* val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value
44+
* }
45+
* import WeekDay._
46+
*
47+
* def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun)
48+
*
49+
* WeekDay.values filter isWorkingDay foreach println
50+
* // output:
51+
* // Mon
52+
* // Tue
53+
* // Wed
54+
* // Thu
55+
* // Fri
56+
* }}}
57+
*
58+
* @example {{{
59+
* // Example of adding attributes to an enumeration by extending the Enumeration.Val class
60+
* object Planet extends Enumeration {
61+
* protected case class PlanetVal(mass: Double, radius: Double) extends super.Val {
62+
* def surfaceGravity: Double = Planet.G * mass / (radius * radius)
63+
* def surfaceWeight(otherMass: Double): Double = otherMass * surfaceGravity
64+
* }
65+
* import scala.language.implicitConversions
66+
* implicit def valueToPlanetVal(x: Value): PlanetVal = x.asInstanceOf[PlanetVal]
67+
*
68+
* val G: Double = 6.67300E-11
69+
* val Mercury = PlanetVal(3.303e+23, 2.4397e6)
70+
* val Venus = PlanetVal(4.869e+24, 6.0518e6)
71+
* val Earth = PlanetVal(5.976e+24, 6.37814e6)
72+
* val Mars = PlanetVal(6.421e+23, 3.3972e6)
73+
* val Jupiter = PlanetVal(1.9e+27, 7.1492e7)
74+
* val Saturn = PlanetVal(5.688e+26, 6.0268e7)
75+
* val Uranus = PlanetVal(8.686e+25, 2.5559e7)
76+
* val Neptune = PlanetVal(1.024e+26, 2.4746e7)
77+
* }
78+
*
79+
* println(Planet.values.filter(_.radius > 7.0e6))
80+
* // output:
81+
* // Planet.ValueSet(Jupiter, Saturn, Uranus, Neptune)
82+
* }}}
83+
*
84+
* @param initial The initial value from which to count the integers that
85+
* identifies values at run-time.
86+
*/
87+
@SerialVersionUID(8476000850333817230L)
88+
abstract class Enumeration (initial: Int) extends Serializable {
89+
thisenum =>
90+
91+
def this() = this(0)
92+
93+
/* Note that `readResolve` cannot be private, since otherwise
94+
the JVM does not invoke it when deserializing subclasses. */
95+
protected def readResolve(): AnyRef = thisenum.getClass.getField(MODULE_INSTANCE_NAME).get(null)
96+
97+
/** The name of this enumeration.
98+
*/
99+
override def toString: String =
100+
((getClass.getName stripSuffix MODULE_SUFFIX_STRING split '.').last split
101+
Regex.quote(NAME_JOIN_STRING)).last
102+
103+
/** The mapping from the integer used to identify values to the actual
104+
* values. */
105+
private val vmap: mutable.Map[Int, Value] = new mutable.HashMap
106+
107+
/** The cache listing all values of this enumeration. */
108+
@transient private var vset: ValueSet = null
109+
@transient @volatile private var vsetDefined = false
110+
111+
/** The mapping from the integer used to identify values to their
112+
* names. */
113+
private[this] val nmap: mutable.Map[Int, String] = new mutable.HashMap
114+
115+
/** The values of this enumeration as a set.
116+
*/
117+
def values: ValueSet = {
118+
if (!vsetDefined) {
119+
vset = (ValueSet.newBuilder ++= vmap.values).result()
120+
vsetDefined = true
121+
}
122+
vset
123+
}
124+
125+
/** The integer to use to identify the next created value. */
126+
protected var nextId: Int = initial
127+
128+
/** The string to use to name the next created value. */
129+
protected var nextName: Iterator[String] = _
130+
131+
private def nextNameOrNull =
132+
if (nextName != null && nextName.hasNext) nextName.next() else null
133+
134+
/** The highest integer amongst those used to identify values in this
135+
* enumeration. */
136+
private[this] var topId = initial
137+
138+
/** The lowest integer amongst those used to identify values in this
139+
* enumeration, but no higher than 0. */
140+
private[this] var bottomId = if(initial < 0) initial else 0
141+
142+
/** The one higher than the highest integer amongst those used to identify
143+
* values in this enumeration. */
144+
final def maxId = topId
145+
146+
/** The value of this enumeration with given id `x`
147+
*/
148+
final def apply(x: Int): Value = vmap(x)
149+
150+
/** Return a `Value` from this `Enumeration` whose name matches
151+
* the argument `s`. The names are determined automatically via reflection.
152+
*
153+
* @param s an `Enumeration` name
154+
* @return the `Value` of this `Enumeration` if its name matches `s`
155+
* @throws NoSuchElementException if no `Value` with a matching
156+
* name is in this `Enumeration`
157+
*/
158+
final def withName(s: String): Value = values.byName.getOrElse(s,
159+
throw new NoSuchElementException(s"No value found for '$s'"))
160+
161+
/** Creates a fresh value, part of this enumeration. */
162+
protected final def Value: Value = Value(nextId)
163+
164+
/** Creates a fresh value, part of this enumeration, identified by the
165+
* integer `i`.
166+
*
167+
* @param i An integer that identifies this value at run-time. It must be
168+
* unique amongst all values of the enumeration.
169+
* @return Fresh value identified by `i`.
170+
*/
171+
protected final def Value(i: Int): Value = Value(i, nextNameOrNull)
172+
173+
/** Creates a fresh value, part of this enumeration, called `name`.
174+
*
175+
* @param name A human-readable name for that value.
176+
* @return Fresh value called `name`.
177+
*/
178+
protected final def Value(name: String): Value = Value(nextId, name)
179+
180+
/** Creates a fresh value, part of this enumeration, called `name`
181+
* and identified by the integer `i`.
182+
*
183+
* @param i An integer that identifies this value at run-time. It must be
184+
* unique amongst all values of the enumeration.
185+
* @param name A human-readable name for that value.
186+
* @return Fresh value with the provided identifier `i` and name `name`.
187+
*/
188+
protected final def Value(i: Int, name: String): Value = new Val(i, name)
189+
190+
private def populateNameMap(): Unit = {
191+
@tailrec def getFields(clazz: Class[_], acc: Array[JField]): Array[JField] = {
192+
if (clazz == null)
193+
acc
194+
else
195+
getFields(clazz.getSuperclass, if (clazz.getDeclaredFields.isEmpty) acc else acc ++ clazz.getDeclaredFields)
196+
}
197+
val fields = getFields(getClass.getSuperclass, getClass.getDeclaredFields)
198+
def isValDef(m: JMethod): Boolean = fields exists (fd => fd.getName == m.getName && fd.getType == m.getReturnType)
199+
200+
// The list of possible Value methods: 0-args which return a conforming type
201+
val methods: Array[JMethod] = getClass.getMethods filter (m => m.getParameterTypes.isEmpty &&
202+
classOf[Value].isAssignableFrom(m.getReturnType) &&
203+
m.getDeclaringClass != classOf[Enumeration] &&
204+
isValDef(m))
205+
methods foreach { m =>
206+
val name = m.getName
207+
// invoke method to obtain actual `Value` instance
208+
val value = m.invoke(this).asInstanceOf[Value]
209+
// verify that outer points to the correct Enumeration: ticket #3616.
210+
if (value.outerEnum eq thisenum) {
211+
val id: Int = value.id
212+
nmap += ((id, name))
213+
}
214+
}
215+
}
216+
217+
/* Obtains the name for the value with id `i`. If no name is cached
218+
* in `nmap`, it populates `nmap` using reflection.
219+
*/
220+
private def nameOf(i: Int): String = synchronized { nmap.getOrElse(i, { populateNameMap() ; nmap(i) }) }
221+
222+
/** The type of the enumerated values. */
223+
@SerialVersionUID(7091335633555234129L)
224+
abstract class Value extends Ordered[Value] with Serializable {
225+
/** the id and bit location of this enumeration value */
226+
def id: Int
227+
/** a marker so we can tell whose values belong to whom come reflective-naming time */
228+
private[Enumeration] val outerEnum = thisenum
229+
230+
override def compare(that: Value): Int =
231+
if (this.id < that.id) -1
232+
else if (this.id == that.id) 0
233+
else 1
234+
override def equals(other: Any): Boolean = other match {
235+
case that: Enumeration#Value => (outerEnum eq that.outerEnum) && (id == that.id)
236+
case _ => false
237+
}
238+
override def hashCode: Int = id.##
239+
240+
/** Create a ValueSet which contains this value and another one */
241+
def + (v: Value): ValueSet = ValueSet(this, v)
242+
}
243+
244+
/** A class implementing the [[scala.Enumeration.Value]] type. This class
245+
* can be overridden to change the enumeration's naming and integer
246+
* identification behaviour.
247+
*/
248+
@SerialVersionUID(0 - 3501153230598116017L)
249+
protected class Val(i: Int, name: String) extends Value with Serializable {
250+
def this(i: Int) = this(i, nextNameOrNull)
251+
def this(name: String) = this(nextId, name)
252+
def this() = this(nextId)
253+
254+
assert(!vmap.isDefinedAt(i), "Duplicate id: " + i)
255+
vmap(i) = this
256+
vsetDefined = false
257+
nextId = i + 1
258+
if (nextId > topId) topId = nextId
259+
if (i < bottomId) bottomId = i
260+
def id: Int = i
261+
override def toString(): String =
262+
if (name != null) name
263+
else try thisenum.nameOf(i)
264+
catch { case _: NoSuchElementException => "<Invalid enum: no field for #" + i + ">" }
265+
266+
protected def readResolve(): AnyRef = {
267+
val enumeration = thisenum.readResolve().asInstanceOf[Enumeration]
268+
if (enumeration.vmap == null) this
269+
else enumeration.vmap(i)
270+
}
271+
}
272+
273+
/** An ordering by id for values of this set */
274+
implicit object ValueOrdering extends Ordering[Value] {
275+
276+
// IMPORTANT:
277+
// Scala 3 removes unnecessary outer pointers while Scala 2 doesn't
278+
// This is important to capture the outer pointer when compiling with
279+
// dotc to maintain our binary compatibility requirements
280+
private val _ = Enumeration.this
281+
def compare(x: Value, y: Value): Int = x compare y
282+
}
283+
284+
/** A class for sets of values.
285+
* Iterating through this set will yield values in increasing order of their ids.
286+
*
287+
* @param nnIds The set of ids of values (adjusted so that the lowest value does
288+
* not fall below zero), organized as a `BitSet`.
289+
* @define Coll `collection.immutable.SortedSet`
290+
*/
291+
@SerialVersionUID(7229671200427364242L)
292+
class ValueSet private[ValueSet] (private[this] var nnIds: immutable.BitSet)
293+
extends immutable.AbstractSet[Value]
294+
with immutable.SortedSet[Value]
295+
with immutable.SortedSetOps[Value, immutable.SortedSet, ValueSet]
296+
with StrictOptimizedIterableOps[Value, immutable.Set, ValueSet]
297+
with Serializable {
298+
299+
implicit def ordering: Ordering[Value] = ValueOrdering
300+
def rangeImpl(from: Option[Value], until: Option[Value]): ValueSet =
301+
new ValueSet(nnIds.rangeImpl(from.map(_.id - bottomId), until.map(_.id - bottomId)))
302+
303+
override def empty: ValueSet = ValueSet.empty
304+
override def knownSize: Int = nnIds.size
305+
override def isEmpty: Boolean = nnIds.isEmpty
306+
def contains(v: Value): Boolean = nnIds contains (v.id - bottomId)
307+
def incl (value: Value): ValueSet = new ValueSet(nnIds + (value.id - bottomId))
308+
def excl (value: Value): ValueSet = new ValueSet(nnIds - (value.id - bottomId))
309+
def iterator: Iterator[Value] = nnIds.iterator map (id => thisenum.apply(bottomId + id))
310+
override def iteratorFrom(start: Value): Iterator[Value] = nnIds iteratorFrom start.id map (id => thisenum.apply(bottomId + id))
311+
override def className: String = s"$thisenum.ValueSet"
312+
/** Creates a bit mask for the zero-adjusted ids in this set as a
313+
* new array of longs */
314+
def toBitMask: Array[Long] = nnIds.toBitMask
315+
316+
override protected def fromSpecific(coll: IterableOnce[Value]): ValueSet = ValueSet.fromSpecific(coll)
317+
override protected def newSpecificBuilder = ValueSet.newBuilder
318+
319+
def map(f: Value => Value): ValueSet = fromSpecific(new View.Map(this, f))
320+
def flatMap(f: Value => IterableOnce[Value]): ValueSet = fromSpecific(new View.FlatMap(this, f))
321+
322+
// necessary for disambiguation:
323+
override def map[B](f: Value => B)(implicit @implicitNotFound(ValueSet.ordMsg) ev: Ordering[B]): immutable.SortedSet[B] =
324+
super[SortedSet].map[B](f)
325+
override def flatMap[B](f: Value => IterableOnce[B])(implicit @implicitNotFound(ValueSet.ordMsg) ev: Ordering[B]): immutable.SortedSet[B] =
326+
super[SortedSet].flatMap[B](f)
327+
override def zip[B](that: IterableOnce[B])(implicit @implicitNotFound(ValueSet.zipOrdMsg) ev: Ordering[(Value, B)]): immutable.SortedSet[(Value, B)] =
328+
super[SortedSet].zip[B](that)
329+
override def collect[B](pf: PartialFunction[Value, B])(implicit @implicitNotFound(ValueSet.ordMsg) ev: Ordering[B]): immutable.SortedSet[B] =
330+
super[SortedSet].collect[B](pf)
331+
332+
@transient private[Enumeration] lazy val byName: Map[String, Value] = iterator.map( v => v.toString -> v).toMap
333+
}
334+
335+
/** A factory object for value sets */
336+
@SerialVersionUID(3L)
337+
object ValueSet extends SpecificIterableFactory[Value, ValueSet] {
338+
private final val ordMsg = "No implicit Ordering[${B}] found to build a SortedSet[${B}]. You may want to upcast to a Set[Value] first by calling `unsorted`."
339+
private final val zipOrdMsg = "No implicit Ordering[${B}] found to build a SortedSet[(Value, ${B})]. You may want to upcast to a Set[Value] first by calling `unsorted`."
340+
341+
/** The empty value set */
342+
val empty: ValueSet = new ValueSet(immutable.BitSet.empty)
343+
/** A value set containing all the values for the zero-adjusted ids
344+
* corresponding to the bits in an array */
345+
def fromBitMask(elems: Array[Long]): ValueSet = new ValueSet(immutable.BitSet.fromBitMask(elems))
346+
/** A builder object for value sets */
347+
def newBuilder: mutable.Builder[Value, ValueSet] = new mutable.Builder[Value, ValueSet] {
348+
private[this] val b = new mutable.BitSet
349+
def addOne (x: Value) = { b += (x.id - bottomId); this }
350+
def clear() = b.clear()
351+
def result() = new ValueSet(b.toImmutable)
352+
}
353+
def fromSpecific(it: IterableOnce[Value]): ValueSet =
354+
newBuilder.addAll(it).result()
355+
}
356+
}

0 commit comments

Comments
 (0)