From cb96f23ce9de4ef7c3ac0d5902c5e4a7edf75331 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 18 May 2022 17:31:24 +0100 Subject: [PATCH] Experimental feature to alias a package --- .../tools/nsc/settings/ScalaSettings.scala | 1 + .../scala/tools/nsc/transform/Erasure.scala | 2 +- .../tools/nsc/typechecker/Analyzer.scala | 17 ++- .../tools/nsc/typechecker/Contexts.scala | 7 +- .../scala/tools/nsc/typechecker/Namers.scala | 43 +++++++- .../scala/tools/nsc/typechecker/Typers.scala | 13 ++- .../scala/reflect/internal/Symbols.scala | 4 +- .../scala/tools/nsc/PackageSmoosherTest.scala | 101 ++++++++++++++++++ 8 files changed, 178 insertions(+), 10 deletions(-) create mode 100644 test/junit/scala/tools/nsc/PackageSmoosherTest.scala diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index cc3b2925626e..d21b4a764d25 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -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 { diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index 4f3f2a8a0768..d26d8086cc28 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -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 diff --git a/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala b/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala index fa0b8bd7869f..fa6f7dcc61e9 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala @@ -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) + } } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 703824097d96..af4d401f9497 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -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: @@ -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 = { diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 48a15b1fb8b1..f010321d1076 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -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) @@ -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 { diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 8c9cec54fd4d..d11319600fbe 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -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) } diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index d8177b7ee608..7c7e38b2cc63 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -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") diff --git a/test/junit/scala/tools/nsc/PackageSmoosherTest.scala b/test/junit/scala/tools/nsc/PackageSmoosherTest.scala new file mode 100644 index 000000000000..e1eb19bf03a4 --- /dev/null +++ b/test/junit/scala/tools/nsc/PackageSmoosherTest.scala @@ -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 + ) + } +}