Skip to content

Commit

Permalink
Experimental feature to alias a package
Browse files Browse the repository at this point in the history
  • Loading branch information
retronym committed Jul 1, 2022
1 parent 9d7ba92 commit cb96f23
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 10 deletions.
1 change: 1 addition & 0 deletions src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ trait ScalaSettings extends StandardScalaSettings with Warnings { _: MutableSett
val YpickleWrite = StringSetting("-Ypickle-write", "directory|jar", "destination for generated .sig files containing type signatures.", "", None).internalOnly()
val YpickleWriteApiOnly = BooleanSetting("-Ypickle-write-api-only", "Exclude private members (other than those material to subclass compilation, such as private trait vals) from generated .sig files containing type signatures.").internalOnly()
val YtrackDependencies = BooleanSetting("-Ytrack-dependencies", "Record references to in unit.depends. Deprecated feature that supports SBT 0.13 with incOptions.withNameHashing(false) only.", default = true)
val YaliasPackage = MultiStringSetting("-Yalias-package", "alias.name=underlying.name", "Alias package")

sealed abstract class CachePolicy(val name: String, val help: String)
object CachePolicy {
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,7 @@ abstract class Erasure extends InfoTransform
} else if (isMethodTypeWithEmptyParams(qual1.tpe)) { // see also adaptToType in TypeAdapter
assert(qual1.symbol.isStable, qual1.symbol)
adaptMember(selectFrom(applyMethodWithEmptyParams(qual1)))
} else if (!qual1.isInstanceOf[Super] && (!isJvmAccessible(qual1.tpe.typeSymbol, context) || !qual1.tpe.typeSymbol.isSubClass(tree.symbol.owner))) {
} else if (!tree.symbol.isTopLevel && !qual1.isInstanceOf[Super] && (!isJvmAccessible(qual1.tpe.typeSymbol, context) || !qual1.tpe.typeSymbol.isSubClass(tree.symbol.owner))) {
// A selection requires a cast:
// - In `(foo: Option[String]).get.trim`, the qualifier has type `Object`. We cast
// to the owner of `trim` (`String`), unless the owner is a non-accessible Java
Expand Down
17 changes: 16 additions & 1 deletion src/compiler/scala/tools/nsc/typechecker/Analyzer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,22 @@ trait Analyzer extends AnyRef
override val checkable = false
override def keepsTypeParams = false

def apply(unit: CompilationUnit): Unit = newNamer(rootContext(unit)).enterSym(unit.body)
override def run(): Unit = {
val aliases: List[String] = settings.YaliasPackage.value
aliases.foreach {
alias =>
alias.split('=').toList match {
case a :: b :: Nil =>
processPackageAliases(a, b)
case _ =>
globalError(s"Cannot parse value for ${settings.YaliasPackage}: $alias")
}
}
super.run()
}
def apply(unit: CompilationUnit) {
newNamer(rootContext(unit)).enterSym(unit.body)
}
}
}

Expand Down
7 changes: 6 additions & 1 deletion src/compiler/scala/tools/nsc/typechecker/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,8 @@ trait Contexts { self: Analyzer =>
else if (mt1 =:= mt2 && name.isTypeName && imp1Symbol.isMonomorphicType && imp2Symbol.isMonomorphicType) {
log(s"Suppressing ambiguous import: $mt1 =:= $mt2 && $imp1Symbol and $imp2Symbol are equivalent")
Some(imp1)
} else if (imp1Symbol == imp2Symbol && imp1Symbol.owner.isTopLevel) {
Some(imp1)
}
else {
log(s"""Import is genuinely ambiguous:
Expand Down Expand Up @@ -1393,7 +1395,10 @@ trait Contexts { self: Analyzer =>
else sym match {
case NoSymbol if inaccessible ne null => inaccessible
case NoSymbol => LookupNotFound
case _ => LookupSucceeded(qual, sym)
case _ =>
if (qual.symbol != null && qual.symbol.isPackage && qual.symbol.moduleClass != sym.owner)
qual.setSymbol(sym.owner.sourceModule)
LookupSucceeded(qual, sym)
}
)
def finishDefSym(sym: Symbol, pre0: Type): NameLookup = {
Expand Down
43 changes: 42 additions & 1 deletion src/compiler/scala/tools/nsc/typechecker/Namers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,48 @@ trait Namers extends MethodSynthesis {
case _ => false
}

def processPackageAliases(package1: String, package2: String): Unit = {
class Pack(sym: Symbol) {
val packageClassSym: Symbol = sym.moduleClass
val declsList = packageClassSym.info.decls.toList
val (packDecls, nonPackDecls) = declsList.partition(_.hasPackageFlag)
val packDeclNames = packDecls.map(_.name).toSet
def enter(sym: Symbol): Unit = {
val scope = packageClassSym.info.decls
if (scope.lookupSymbolEntry(sym) == null) {
scope.enter(sym)
}
}
}
def smoosh(p1: Pack, p2: Pack): Unit = {
p2.nonPackDecls.foreach(p1.enter(_))

val sharedNames = p1.packDeclNames.intersect(p2.packDeclNames)
p1.packDecls.foreach { p1Decl =>
p2.packDecls.find(_.name == p1Decl.name) match {
case None =>
case Some(p2Decl) =>
smoosh(new Pack(p1Decl), new Pack(p2Decl))
}
}
p2.packDecls.foreach { p2Decl =>
if (sharedNames.contains(p2Decl.name)) {
val p1Decl = p1.packDecls.find(_.name == p2Decl.name).get
smoosh(new Pack(p1Decl), new Pack(p2Decl))
} else {
val dummySelect = Select(gen.mkAttributedRef(p1.packageClassSym.sourceModule), p2Decl.name)
val p2DeclClone = newNamer(NoContext.make(EmptyTree, RootClass)).createPackageSymbol(NoPosition, dummySelect)
p1.enter(p2DeclClone)
}
}
}
val package1Sym = rootMirror.getPackageIfDefined(package1)
val package2Sym = rootMirror.getPackageIfDefined(package2)
// for now require both packages to be defined
if (package1Sym != NoSymbol && package2Sym != NoSymbol)
smoosh(new Pack(package1Sym), new Pack(package2Sym))
}

private class NormalNamer(context: Context) extends Namer(context)
def newNamer(context: Context): Namer = new NormalNamer(context)

Expand Down Expand Up @@ -370,7 +412,6 @@ trait Namers extends MethodSynthesis {
case x => throw new MatchError(x)
}
val existing = pkgOwner.info.decls.lookup(pid.name)

if (existing.hasPackageFlag && pkgOwner == existing.owner)
existing
else {
Expand Down
13 changes: 9 additions & 4 deletions src/compiler/scala/tools/nsc/typechecker/Typers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -553,13 +553,18 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
private def makeAccessible(tree: Tree, sym: Symbol, pre: Type, site: Tree): Any /*Type | (Tree, Type)*/ =
if (!unit.isJava && context.isInPackageObject(sym, pre.typeSymbol)) {
val qual = typedQualifier { atPos(tree.pos.makeTransparent) {
def packageObject =
if (!sym.isOverloaded && sym.owner.isModuleClass) sym.owner.sourceModule // historical optimization, perhaps no longer needed
else pre.typeSymbol.packageObject
tree match {
case Ident(_) =>
val packageObject =
if (!sym.isOverloaded && sym.owner.isModuleClass) sym.owner.sourceModule // historical optimization, perhaps no longer needed
else pre.typeSymbol.packageObject
Ident(packageObject)
case Select(qual, _) => Select(qual, nme.PACKAGEkw)
case Select(qual, _) =>
if (qual.symbol != sym.owner.owner) {
Ident(packageObject)
} else {
Select(qual, nme.PACKAGEkw)
}
case SelectFromTypeTree(qual, _) => Select(qual, nme.PACKAGEkw)
case x => throw new MatchError(x)
}
Expand Down
4 changes: 2 additions & 2 deletions src/reflect/scala/reflect/internal/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2691,8 +2691,8 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
else if (isAnonymousClass) ("anonymous class", "anonymous class", "AC")
else if (isRefinementClass) ("refinement class", "", "RC")
else if (isJavaAnnotation) ("Java annotation", "Java annotation", "JANN")
else if (isJavaEnum
|| companion.isJavaEnum) ("Java enumeration", "Java enum", "JENUM")
// else if (isJavaEnum
// || companion.isJavaEnum) ("Java enumeration", "Java enum", "JENUM")
else if (isJava && isModule) ("Java module", "class", "JMOD")
else if (isJava && isModuleClass) ("Java module class", "class", "JMODC")
else if (isModule) ("module", "object", "MOD")
Expand Down
101 changes: 101 additions & 0 deletions test/junit/scala/tools/nsc/PackageSmoosherTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package scala.tools.nsc

import java.nio.file.Files

object PackageSmoosherTest {
def main(args: Array[String]): Unit = {
val g = new Global(new Settings)
g.settings.usejavacp.value = true
import g._
val tmpDir = Files.createTempDirectory("test-classes-")

val code =
s"""
package o {
package platform {
package a {
class A
object `package` {}
object SomeObject
}
object `package` {
implicit def foo: a.A = null
}
object SomeObject
}
package prime {
package b {
class B
}
package a {
class A2
}
package q {
class Query
}
class Query
}
}
"""

def compile(enabled: Boolean)(code: String) = {
settings.Xprint.value = List("all")
settings.outdir.value = tmpDir.toAbsolutePath.toString
if (enabled)
settings.YaliasPackage.value = "o.prime=o.platform" :: Nil
reporter.reset()
val r = new Run
r.compileSources(newSourceFile(code) :: Nil)
assert(!reporter.hasErrors)
}

compile(enabled = false)(code)
compile(enabled = true)(
"""
| package client {
| object Client {
| new o.platform.a.A
| new o.prime.a.A
| o.prime.SomeObject
| o.prime.a.SomeObject
| }
| }
|""".stripMargin)
compile(enabled = true)(
"""
|import o.platform._
|import o.prime._
|class Test {
| foo
| implicitly[o.platform.a.A]
|}
|""".stripMargin)
compile(enabled = true)(
"""
|package o.platform.bt
|object `package` {
|}
|object O1
|trait T1
|""".stripMargin
)
compile(enabled = true)(
"""
|package o.prime.bt
|
|class C2
|""".stripMargin
)

compile(enabled = true)(
"""
|import o.platform._
|import o.prime.q._
|
|class Test extends Query {
|
|}
|""".stripMargin
)
}
}

0 comments on commit cb96f23

Please sign in to comment.