Skip to content

Experimental feature to alias a package [2.12] #120

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

Open
wants to merge 3 commits into
base: 2.12.x
Choose a base branch
from
Open
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
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 @@ -265,6 +265,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 @@ -746,7 +746,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
16 changes: 15 additions & 1 deletion src/compiler/scala/tools/nsc/typechecker/Analyzer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,21 @@ trait Analyzer extends AnyRef
}
}

def apply(unit: CompilationUnit) {
override def run(): Unit = {
super.run()
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")
}
}
}

def apply(unit: CompilationUnit): Unit = {
openPackageObjectsTraverser(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 @@ -1016,6 +1016,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:\n " + characterize)
Expand Down Expand Up @@ -1145,7 +1147,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
44 changes: 43 additions & 1 deletion src/compiler/scala/tools/nsc/typechecker/Namers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,49 @@ trait Namers extends MethodSynthesis {
case _ => false
}

def processPackageAliases(package1: String, package2: String): Unit = {
class Pack(val 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)
smoosh(new Pack(p2DeclClone), new Pack(p2Decl))
}
}
}
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 @@ -362,7 +405,6 @@ trait Namers extends MethodSynthesis {
case Select(qual: RefTree, _) => createPackageSymbol(pos, qual).moduleClass
}
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 @@ -571,13 +571,18 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
}
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)
}
}}
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 @@ -2647,8 +2647,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
222 changes: 222 additions & 0 deletions test/junit/scala/tools/nsc/PackageSmoosherTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
package scala.tools.nsc

import org.junit.Test
import org.junit.rules.TemporaryFolder

import java.nio.file.Files

class PackageSmoosherTest {
class Fixture(aliases: List[String]) {
val tmpDir = new TemporaryFolder()
tmpDir.create()
def compile(enabled: Boolean)(code: String) = {
val g = new Global(new Settings)
g.settings.usejavacp.value = true
import g._
// settings.Xprint.value = List("typer")
settings.classpath.value = tmpDir.getRoot.getAbsolutePath
settings.outdir.value = tmpDir.getRoot.getAbsolutePath
if (enabled)
settings.YaliasPackage.value = aliases
reporter.reset()
val r = new Run
r.compileSources(newSourceFile(code) :: Nil)
assert(!reporter.hasErrors)
}
def close(): Unit = {
tmpDir.delete()
}
}
def withFixture(aliases: List[String])(f: Fixture => Unit): Unit = {
val fixture = new Fixture(aliases)
try f(fixture)
finally fixture.close()
}
@Test
def test(): Unit = withFixture("o.prime=o.platform" :: Nil) { f =>
import f._

val code =
s"""
package o {
package platform {
package a {
class A
object `package` {}
object SomeObject
package c {
class platform_a_c_Class
}
}
object `package` {
implicit def foo: a.A = null
val bar = ""
}
object SomeObject

}
package prime {
package b {
class B
}
package a {
class A2
}
package q {
class Query
}
class Query
}
}
"""

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
)

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

@Test
def testPackageObjectImport(): Unit = withFixture( "o.prime.x.y=o.platform.x.y" :: Nil) { f =>
import f._

compile(enabled = false)(
"""
|package o.platform.x
|
|package object y {
| val someVal = ""
|}
|""".stripMargin)
compile(enabled = false)(
"""
|package o.prime.x.y
|
|private class Placeholder
|""".stripMargin)

compile(enabled = true)(
"""
|package o.prime.x
|
|import o.prime.x.y.{someVal => _, _}
|
|class Test {
|}
|""".stripMargin)
}


@Test
def testPackageObjectImport2(): Unit = withFixture("o.prime=o.platform" :: Nil) { f =>
import f._

compile(enabled = false)(
"""
|package o.platform.x
|
|package object y {
| val someVal = ""
|}
|""".stripMargin)

compile(enabled = false)(
"""
|package o.prime
|
|class Placeholder
|""".stripMargin)

compile(enabled = true)(
"""
|package com.acme
|
|import o.prime.x.y.{someVal => _, _}
|
|class Test {
|}
|""".stripMargin)
}

@Test
def testNesting(): Unit = withFixture("o.prime=o.platform" :: Nil) { f =>
import f._

compile(enabled = false)(
"""
|package o.platform.x.y
|
|class InXY
|""".stripMargin)
compile(enabled = false)(
"""
|package o.prime
|
|private class Placeholder
|""".stripMargin)

compile(enabled = true)(
"""
|package other
|
|class Test {
| new o.prime.x.y.InXY
|}
|""".stripMargin)
}
}