Skip to content

Drop scala shadowing and DottyPredef #10428

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

Merged
merged 13 commits into from
Nov 24, 2020
2 changes: 1 addition & 1 deletion community-build/community-projects/endpoints4s
2 changes: 1 addition & 1 deletion community-build/community-projects/scalaz
Submodule scalaz updated 1 files
+2 −1 project/build.scala
2 changes: 0 additions & 2 deletions compiler/src/dotty/tools/dotc/Driver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,6 @@ class Driver {
inContext(ictx) {
if !ctx.settings.YdropComments.value || ctx.mode.is(Mode.ReadComments) then
ictx.setProperty(ContextDoc, new ContextDocstrings)
if Feature.enabledBySetting(nme.Scala2Compat) then
report.warning("-language:Scala2Compat will go away; use -source 3.0-migration instead")
val fileNames = CompilerCommand.checkUsage(summary, sourcesRequired)
fromTastySetup(fileNames, ctx)
}
Expand Down
3 changes: 1 addition & 2 deletions compiler/src/dotty/tools/dotc/config/Feature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,7 @@ object Feature:
case Some(v) => v
case none => sourceVersionSetting

def migrateTo3(using Context): Boolean =
sourceVersion == `3.0-migration` || enabledBySetting(nme.Scala2Compat)
def migrateTo3(using Context): Boolean = sourceVersion == `3.0-migration`

/** If current source migrates to `version`, issue given warning message
* and return `true`, otherwise return `false`.
Expand Down
67 changes: 64 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import unpickleScala2.Scala2Unpickler.ensureConstructor
import scala.collection.mutable
import collection.mutable
import Denotations.SingleDenotation
import util.SimpleIdentityMap
import util.{SimpleIdentityMap, SourceFile, NoSource}
import typer.ImportInfo.RootRef

import scala.annotation.tailrec
Expand Down Expand Up @@ -252,6 +252,7 @@ class Definitions {
* or classes with the same name. But their binary artifacts are
* in `scalaShadowing` so they don't clash with the same-named `scala`
* members at runtime.
* It is used only for non-bootstrapped code
*/
@tu lazy val ScalaShadowingPackage: TermSymbol = requiredPackage(nme.scalaShadowing)

Expand Down Expand Up @@ -481,12 +482,12 @@ class Definitions {
newPermanentSymbol(ScalaPackageClass, tpnme.IMPLICITkw, EmptyFlags, TypeBounds.empty).entered
def ImplicitScrutineeTypeRef: TypeRef = ImplicitScrutineeTypeSym.typeRef


@tu lazy val ScalaPredefModule: Symbol = requiredModule("scala.Predef")
@tu lazy val Predef_conforms : Symbol = ScalaPredefModule.requiredMethod(nme.conforms_)
@tu lazy val Predef_classOf : Symbol = ScalaPredefModule.requiredMethod(nme.classOf)
@tu lazy val Predef_identity : Symbol = ScalaPredefModule.requiredMethod(nme.identity)
@tu lazy val Predef_undefined: Symbol = ScalaPredefModule.requiredMethod(nme.???)
@tu lazy val ScalaPredefModuleClass: ClassSymbol = ScalaPredefModule.moduleClass.asClass

@tu lazy val SubTypeClass: ClassSymbol = requiredClass("scala.<:<")
@tu lazy val SubType_refl: Symbol = SubTypeClass.companionModule.requiredMethod(nme.refl)
Expand All @@ -510,7 +511,7 @@ class Definitions {
// will return "null" when called recursively, see #1856.
def DottyPredefModule: Symbol = {
if (myDottyPredefModule == null) {
myDottyPredefModule = requiredModule("dotty.DottyPredef")
myDottyPredefModule = getModuleIfDefined("dotty.DottyPredef")
assert(myDottyPredefModule != null)
}
myDottyPredefModule
Expand Down Expand Up @@ -782,6 +783,7 @@ class Definitions {
@tu lazy val Mirror_SingletonProxyClass: ClassSymbol = requiredClass("scala.deriving.Mirror.SingletonProxy")

@tu lazy val LanguageModule: Symbol = requiredModule("scala.language")
@tu lazy val LanguageModuleClass: Symbol = LanguageModule.moduleClass.asClass
@tu lazy val LanguageExperimentalModule: Symbol = requiredModule("scala.language.experimental")
@tu lazy val NonLocalReturnControlClass: ClassSymbol = requiredClass("scala.runtime.NonLocalReturnControl")
@tu lazy val SelectableClass: ClassSymbol = requiredClass("scala.Selectable")
Expand Down Expand Up @@ -1101,6 +1103,65 @@ class Definitions {
|| sym.owner == CompiletimeOpsPackageObjectString.moduleClass && compiletimePackageStringTypes.contains(sym.name)
)

// ----- Scala-2 library patches --------------------------------------

/** The `scala.runtime.stdLibPacthes` package contains objects
* that contain defnitions that get added as members to standard library
* objects with the same name.
*/
@tu lazy val StdLibPatchesPackage: TermSymbol = requiredPackage("scala.runtime.stdLibPatches")
@tu private lazy val ScalaPredefModuleClassPatch: Symbol = getModuleIfDefined("scala.runtime.stdLibPatches.Predef").moduleClass
@tu private lazy val LanguageModuleClassPatch: Symbol = getModuleIfDefined("scala.runtime.stdLibPatches.language").moduleClass

/** If `sym` is a patched library class, the source file of its patch class,
* otherwise `NoSource`
*/
def patchSource(sym: Symbol)(using Context): SourceFile =
if sym == ScalaPredefModuleClass then ScalaPredefModuleClassPatch.source
else if sym == LanguageModuleClass then LanguageModuleClassPatch.source
else NoSource

/** A finalizer that patches standard library classes.
* It copies all non-private, non-synthetic definitions from `patchCls`
* to `denot` while changing their owners to `denot`. Before that it deletes
* any definitions of `denot` that have the same name as one of the copied
* definitions.
*
* If an object is present in both the original class and the patch class,
* it is not overwritten. Instead its members are copied recursively.
*
* To avpid running into cycles on bootstrap, patching happens only if `patchCls`
* is read from a classfile.
*/
def patchStdLibClass(denot: ClassDenotation)(using Context): Unit =

def patch2(denot: ClassDenotation, patchCls: Symbol): Unit =
val scope = denot.info.decls.openForMutations
def recurse(patch: Symbol) = patch.is(Module) && scope.lookup(patch.name).exists
if patchCls.exists then
val patches = patchCls.info.decls.filter(patch =>
!patch.isConstructor && !patch.isOneOf(PrivateOrSynthetic))
for patch <- patches if !recurse(patch) do
val e = scope.lookupEntry(patch.name)
if e != null then scope.unlink(e)
for patch <- patches do
patch.ensureCompleted()
if !recurse(patch) then
patch.denot = patch.denot.copySymDenotation(owner = denot.symbol)
scope.enter(patch)
else if patch.isClass then
patch2(scope.lookup(patch.name).asClass, patch)

def patchWith(patchCls: Symbol) =
denot.sourceModule.info = denot.typeRef // we run into a cyclic reference when patching if this line is omitted
patch2(denot, patchCls)

if denot.name == tpnme.Predef.moduleClassName && denot.symbol == ScalaPredefModuleClass then
patchWith(ScalaPredefModuleClassPatch)
else if denot.name == tpnme.language.moduleClassName && denot.symbol == LanguageModuleClass then
patchWith(LanguageModuleClassPatch)
end patchStdLibClass

// ----- Symbol sets ---------------------------------------------------

@tu lazy val AbstractFunctionType: Array[TypeRef] = mkArityArray("scala.runtime.AbstractFunction", MaxImplementedFunctionArity, 0)
Expand Down
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,6 @@ object StdNames {
val Ref: N = "Ref"
val RootPackage: N = "RootPackage"
val RootClass: N = "RootClass"
val Scala2Compat: N = "Scala2Compat"
val Select: N = "Select"
val Shape: N = "Shape"
val StringContext: N = "StringContext"
Expand Down
31 changes: 18 additions & 13 deletions compiler/src/dotty/tools/dotc/core/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -428,23 +428,21 @@ object Symbols {
private var mySource: SourceFile = NoSource

final def sourceOfClass(using Context): SourceFile = {
if (!mySource.exists && !denot.is(Package))
if !mySource.exists && !denot.is(Package) then
// this allows sources to be added in annotations after `sourceOfClass` is first called
mySource = {
val file = associatedFile
if (file != null && file.extension != "class") ctx.getSource(file)
else {
def sourceFromTopLevel(using Context) =
denot.topLevelClass.unforcedAnnotation(defn.SourceFileAnnot) match {
case Some(sourceAnnot) => sourceAnnot.argumentConstant(0) match {
val file = associatedFile
if file != null && file.extension != "class" then
mySource = ctx.getSource(file)
else
mySource = defn.patchSource(this)
if !mySource.exists then
mySource = atPhaseNoLater(flattenPhase) {
denot.topLevelClass.unforcedAnnotation(defn.SourceFileAnnot) match
case Some(sourceAnnot) => sourceAnnot.argumentConstant(0) match
case Some(Constant(path: String)) => ctx.getSource(path)
case none => NoSource
}
case none => NoSource
}
atPhaseNoLater(flattenPhase)(sourceFromTopLevel)
}
}
}
mySource
}

Expand Down Expand Up @@ -880,6 +878,13 @@ object Symbols {
staticRef(name).requiredSymbol("object", name)(_.is(Module)).asTerm
}

/** Get module symbol if the module is either defined in current compilation run
* or present on classpath. Returns NoSymbol otherwise.
*/
def getModuleIfDefined(path: PreName)(using Context): Symbol =
staticRef(path.toTermName, generateStubs = false)
.disambiguate(_.is(Module)).symbol

def requiredModuleRef(path: PreName)(using Context): TermRef = requiredModule(path).termRef

def requiredMethod(path: PreName)(using Context): TermSymbol = {
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -764,7 +764,9 @@ class TreePickler(pickler: TastyPickler) {

def pickle(trees: List[Tree])(using Context): Unit = {
trees.foreach(tree => if (!tree.isEmpty) pickleTree(tree))
def missing = forwardSymRefs.keysIterator.map(sym => sym.showLocated + "(line " + sym.srcPos.line + ")").toList
def missing = forwardSymRefs.keysIterator
.map(sym => i"${sym.showLocated} (line ${sym.srcPos.line}) #${sym.id}")
.toList
assert(forwardSymRefs.isEmpty, i"unresolved symbols: $missing%, % when pickling ${ctx.source}")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,7 @@ class TreeUnpickler(reader: TastyReader,
val stats = rdr.readIndexedStats(localDummy, end)
tparams ++ vparams ++ stats
})
defn.patchStdLibClass(cls)
setSpan(start,
untpd.Template(constr, mappedParents, Nil, self, lazyStats)
.withType(localDummy.termRef))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ object Scala2Unpickler {

denot.info = tempInfo.finalized(normalizedParents)
denot.ensureTypeParamsInCorrectOrder()
defn.patchStdLibClass(denot)
}
}

Expand Down
12 changes: 9 additions & 3 deletions compiler/src/dotty/tools/dotc/transform/TreeChecker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -446,9 +446,9 @@ class TreeChecker extends Phase with SymTransformer {
val symbolsNotDefined = decls -- defined - constr.symbol

assert(symbolsNotDefined.isEmpty,
i" $cls tree does not define members: ${symbolsNotDefined.toList}%, %\n" +
i"expected: ${decls.toList}%, %\n" +
i"defined: ${defined}%, %")
i" $cls tree does not define members: ${symbolsNotDefined.toList}%, %\n" +
i"expected: ${decls.toList}%, %\n" +
i"defined: ${defined}%, %")

super.typedClassDef(cdef, cls)
}
Expand Down Expand Up @@ -540,6 +540,12 @@ class TreeChecker extends Phase with SymTransformer {
super.typedWhileDo(tree)
}

override def typedPackageDef(tree: untpd.PackageDef)(using Context): Tree =
if tree.symbol == defn.StdLibPatchesPackage then
promote(tree) // don't check stdlib patches, since their symbols were highjacked by stdlib classes
else
super.typedPackageDef(tree)

override def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol])(using Context): Tree =
tree

Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,7 @@ class Namer { typer: Typer =>
cls.setNoInitsFlags(parentsKind(parents), untpd.bodyKind(rest))
if (cls.isNoInitsClass) cls.primaryConstructor.setFlag(StableRealizable)
processExports(using localCtx)
defn.patchStdLibClass(denot.asClass)
Copy link
Member

Choose a reason for hiding this comment

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

What happens when we pickle Predef.scala to Tasty, then unpickle it? As far as I can tell, since we're only adding symbols and not trees, the pickled Predef won't have the extra definitions, so we need to run patchStdlibClass in TreeUnpickler too.

Copy link
Contributor Author

@odersky odersky Nov 23, 2020

Choose a reason for hiding this comment

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

But do we ever unpickle Predef from Tasty? Why would we want to do that?

Copy link
Contributor

Choose a reason for hiding this comment

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

The doctool should probably see the unpickled Predef, for definition lookup purposes. I'd also worry about any user of Tasty Inspector that wants to look up definitions in Predef.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But so far nobody pickles Predef into Tasty, right?

Copy link
Contributor

Choose a reason for hiding this comment

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

I checked and it seems a Predef.tasty file is created, we also document it in the doctool already:
https://scala3doc.virtuslab.com/pr-master/scala3-stdlib/api/scala/-predef/index.html?query=%20object%20Predef%20extends%20LowPriorityImplicits

}
}

Expand Down
3 changes: 3 additions & 0 deletions library/src-bootstrapped/dotty/DottyPredef.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package dotty

object DottyPredef
3 changes: 3 additions & 0 deletions library/src-bootstrapped/scalaShadowing/D_u_m_m_y.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package scalaShadowing

class D_u_m_m_y
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,6 @@ object language {
object genericNumberLiterals
}

/** Where imported, a backwards compatibility mode for Scala2 is enabled */
object Scala2Compat

/** Where imported, auto-tupling is disabled */
object noAutoTupling

Expand Down
47 changes: 47 additions & 0 deletions library/src/scala/runtime/stdLibPatches/Predef.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package scala.runtime.stdLibPatches

object Predef:
import compiletime.summonFrom

inline def assert(inline assertion: Boolean, inline message: => Any): Unit =
if !assertion then scala.runtime.Scala3RunTime.assertFailed(message)

inline def assert(inline assertion: Boolean): Unit =
if !assertion then scala.runtime.Scala3RunTime.assertFailed()

/**
* Retrieve the single value of a type with a unique inhabitant.
*
* @example {{{
* object Foo
* val foo = valueOf[Foo.type]
* // foo is Foo.type = Foo
*
* val bar = valueOf[23]
* // bar is 23.type = 23
* }}}
* @group utilities
*/
inline def valueOf[T]: T = summonFrom {
case ev: ValueOf[T] => ev.value
}

/** Summon a given value of type `T`. Usually, the argument is not passed explicitly.
*
* @tparam T the type of the value to be summoned
* @return the given value typed as the provided type parameter
*/
inline def summon[T](using x: T): x.type = x

// Extension methods for working with explicit nulls

/** Strips away the nullability from a value.
* e.g.
* val s1: String|Null = "hello"
* val s: String = s1.nn
*
* Note that `.nn` performs a checked cast, so if invoked on a null value it'll throw an NPE.
*/
extension [T](x: T | Null) inline def nn: x.type & T =
scala.runtime.Scala3RunTime.nn(x)
end Predef
60 changes: 60 additions & 0 deletions library/src/scala/runtime/stdLibPatches/language.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package scala.runtime.stdLibPatches

/** Scala 3 additions and replacements to the `scala.language` object.
*/
object language:

/** The experimental object contains features that have been recently added but have not
* been thoroughly tested in production yet.
*
* Experimental features '''may undergo API changes''' in future releases, so production
* code should not rely on them.
*
* Programmers are encouraged to try out experimental features and
* [[http://issues.scala-lang.org report any bugs or API inconsistencies]]
* they encounter so they can be improved in future releases.
*
* @group experimental
*/
object experimental:

/** Experimental support for richer dependent types */
object dependent

/** Experimental support for named type arguments */
object namedTypeArguments

/** Experimental support for generic number literals */
object genericNumberLiterals
end experimental

/** Where imported, auto-tupling is disabled */
object noAutoTupling

/** Where imported, loose equality using eqAny is disabled */
object strictEquality

/** Where imported, ad hoc extensions of non-open classes in other
* compilation units are allowed.
*
* '''Why control the feature?''' Ad-hoc extensions should usually be avoided
* since they typically cannot rely on an "internal" contract between a class
* and its extensions. Only open classes need to specify such a contract.
* Ad-hoc extensions might break for future versions of the extended class,
* since the extended class is free to change its implementation without
* being constrained by an internal contract.
*
* '''Why allow it?''' An ad-hoc extension can sometimes be necessary,
* for instance when mocking a class in a testing framework, or to work
* around a bug or missing feature in the original class. Nevertheless,
* such extensions should be limited in scope and clearly documented.
* That's why the language import is required for them.
*/
object adhocExtensions

/** Source version */
object `3.0-migration`
object `3.0`
object `3.1-migration`
object `3.1`
end language
Loading