diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 21d44d9a4daa..fca4af3d55e6 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -7,6 +7,7 @@ import java.nio.CharBuffer import scala.io.Codec import Int.MaxValue import Names._, StdNames._, Contexts._, Symbols._, Flags._, NameKinds._, Types._ +import SymDenotations.PrefixSeparator import util.Chars.{isOperatorPart, digit2int} import Definitions._ import nme._ @@ -141,7 +142,7 @@ object NameOps { * followed by `kind` and the name. */ def expandedName(base: Symbol, kind: QualifiedNameKind = ExpandedName)(using Context): N = - likeSpacedN { base.fullNameSeparated(ExpandPrefixName, kind, name) } + likeSpacedN { base.fullNameSeparated(PrefixSeparator.ExpandPrefix, kind, name) } /** Revert the expanded name. */ def unexpandedName: N = likeSpacedN { diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 96c629517719..31bd81272052 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -455,17 +455,18 @@ object SymDenotations { final def originalOwner(using Context): Symbol = initial.maybeOwner /** The encoded full path name of this denotation, where outer names and inner names - * are separated by `separator` strings as indicated by the given name kind. + * are separated by `separator` strings as indicated by the given prefix separator. * Drops package objects. Represents each term in the owner chain by a simple `_$`. */ - def fullNameSeparated(kind: QualifiedNameKind)(using Context): Name = - maybeOwner.fullNameSeparated(kind, kind, name) + def fullNameSeparated(prefixSep: PrefixSeparator)(using Context): Name = + val doEncodeName = prefixSep.encodePackages && is(Package) && !isEffectiveRoot + maybeOwner.fullNameSeparated(prefixSep, prefixSep.kind, if doEncodeName then name.encode else name) - /** The encoded full path name of this denotation (separated by `prefixKind`), + /** The encoded full path name of this denotation (separated by `prefixSep.kind`), * followed by the separator implied by `kind` and the given `name`. * Drops package objects. Represents each term in the owner chain by a simple `_$`. */ - def fullNameSeparated(prefixKind: QualifiedNameKind, kind: QualifiedNameKind, name: Name)(using Context): Name = + def fullNameSeparated(prefixSep: PrefixSeparator, kind: QualifiedNameKind, name: Name)(using Context): Name = if (symbol == NoSymbol || isEffectiveRoot || kind == FlatName && is(PackageClass)) name else { @@ -475,7 +476,7 @@ object SymDenotations { encl = encl.owner filler += "_$" } - var prefix = encl.fullNameSeparated(prefixKind) + var prefix = encl.fullNameSeparated(prefixSep) if (kind.separator == "$") // duplicate scalac's behavior: don't write a double '$$' for module class members. prefix = prefix.exclude(ModuleClassName) @@ -490,10 +491,13 @@ object SymDenotations { } /** The encoded flat name of this denotation, where joined names are separated by `separator` characters. */ - def flatName(using Context): Name = fullNameSeparated(FlatName) + def flatName(using Context): Name = fullNameSeparated(PrefixSeparator.Flat) /** `fullName` where `.' is the separator character */ - def fullName(using Context): Name = fullNameSeparated(QualifiedName) + def fullName(using Context): Name = fullNameSeparated(PrefixSeparator.Full) + + /** `fullName` where `.' is the separator character, and package names are encoded */ + def tastyCompatibleFullName(using Context): Name = fullNameSeparated(PrefixSeparator.TastyCompatibleFull) private var myTargetName: Name = null @@ -1685,6 +1689,12 @@ object SymDenotations { final def sealedDescendants(using Context): List[Symbol] = this.symbol :: sealedStrictDescendants } + enum PrefixSeparator(final val kind: QualifiedNameKind, final val encodePackages: Boolean): + case TastyCompatibleFull extends PrefixSeparator(QualifiedName, encodePackages = true) + case Full extends PrefixSeparator(QualifiedName, encodePackages = false) + case Flat extends PrefixSeparator(FlatName, encodePackages = false) + case ExpandPrefix extends PrefixSeparator(ExpandPrefixName, encodePackages = false) + /** The contents of a class definition during a period */ class ClassDenotation private[SymDenotations] ( @@ -1701,7 +1711,7 @@ object SymDenotations { // ----- caches ------------------------------------------------------- private var myTypeParams: List[TypeSymbol] = null - private var fullNameCache: SimpleIdentityMap[QualifiedNameKind, Name] = SimpleIdentityMap.empty + private var fullNameCache: SimpleIdentityMap[PrefixSeparator, Name] = SimpleIdentityMap.empty private var myMemberCache: EqHashMap[Name, PreDenotation] = null private var myMemberCachePeriod: Period = Nowhere @@ -2252,12 +2262,12 @@ object SymDenotations { } } - override final def fullNameSeparated(kind: QualifiedNameKind)(using Context): Name = { - val cached = fullNameCache(kind) + override final def fullNameSeparated(prefixKind: PrefixSeparator)(using Context): Name = { + val cached = fullNameCache(prefixKind) if (cached != null) cached else { - val fn = super.fullNameSeparated(kind) - fullNameCache = fullNameCache.updated(kind, fn) + val fn = super.fullNameSeparated(prefixKind) + fullNameCache = fullNameCache.updated(prefixKind, fn) fn } } diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 1473bcc559e2..9454d550551d 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -799,15 +799,15 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst sigName(defn.functionTypeErasure(sym)) else val cls = normalizeClass(sym.asClass) - val fullName = + val tastyCompatibleFullName = if !ctx.erasedTypes then // It's important to use the initial symbol to compute the full name // because the current symbol might have a different name or owner // and signatures are required to be stable before erasure. - cls.initial.fullName + cls.initial.tastyCompatibleFullName else - cls.fullName - fullName.asTypeName + cls.tastyCompatibleFullName + tastyCompatibleFullName.asTypeName case tp: AppliedType => val sym = tp.tycon.typeSymbol sigName( // todo: what about repeatedParam? diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index c8bffbc17252..6626fb60caa5 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -191,7 +191,7 @@ class TreePickler(pickler: TastyPickler) { } if (sym.is(Flags.Package)) { writeByte(if (tpe.isType) TYPEREFpkg else TERMREFpkg) - pickleName(sym.fullName) + pickleName(sym.tastyCompatibleFullName) } else if (tpe.prefix == NoPrefix) { writeByte(if (tpe.isType) TYPEREFdirect else TERMREFdirect) @@ -397,7 +397,8 @@ class TreePickler(pickler: TastyPickler) { || tree.denot.asSingleDenotation.isRefinedMethod // refined methods have no defining class symbol if selectFromQualifier then writeByte(if name.isTypeName then SELECTtpt else SELECT) - pickleNameAndSig(name, sig, ename) + val isPackage = tree.symbol.is(Package) + pickleNameAndSig(if isPackage then name.encode else name, sig, ename) pickleTree(qual) else // select from owner writeByte(SELECTin) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 157b424b6251..9c706621a44b 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -270,7 +270,9 @@ class PlainPrinter(_ctx: Context) extends Printer { ParamRefNameString(param.binder.paramNames(param.paramNum)) /** The name of the symbol without a unique id. */ - protected def simpleNameString(sym: Symbol): String = nameString(sym.name) + protected def simpleNameString(sym: Symbol): String = + val doEncode = homogenizedView && sym.is(Package) + nameString(if doEncode then sym.name.encode else sym.name) /** If -uniqid is set, the hashcode of the lambda type, after a # */ protected def lambdaHash(pt: LambdaType): Text = @@ -308,7 +310,13 @@ class PlainPrinter(_ctx: Context) extends Printer { protected def selectionString(tp: NamedType): String = { val sym = if (homogenizedView) tp.symbol else tp.currentSymbol - if (sym.exists) nameString(sym) else nameString(tp.name) + if sym.exists then + if homogenizedView && sym.is(Flags.Package) && !sym.isEffectiveRoot then + nameString(sym.name.encode) + else + nameString(sym) + else + nameString(tp.name) } /** The string representation of this type used as a prefix */ @@ -674,4 +682,3 @@ class PlainPrinter(_ctx: Context) extends Printer { protected def coloredText(text: Text, color: String): Text = if (ctx.useColors) color ~ text ~ SyntaxHighlighting.NoColor else text } - diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 11e90896287d..00a878a9a842 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -82,7 +82,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { else super.nameString(strippedName) override protected def simpleNameString(sym: Symbol): String = - nameString(if (ctx.property(XprintMode).isEmpty) sym.initial.name else sym.name) + val theSym: SymDenotation = if (ctx.property(XprintMode).isEmpty) sym.initial else sym + nameString(if homogenizedView && theSym.is(Package) then theSym.name.encode else theSym.name) override def fullNameString(sym: Symbol): String = if !sym.exists || isEmptyPrefix(sym.effectiveOwner) then nameString(sym) diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index 8c34d5f3186d..780e2e5f6a45 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -8,11 +8,11 @@ import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.core.Decorators._ import dotty.tools.dotc.core.Flags._ -import dotty.tools.dotc.core.NameKinds.FlatName import dotty.tools.dotc.core.Names.Name import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.SymDenotations.PrefixSeparator import dotty.tools.dotc.core.Denotations.staticRef import dotty.tools.dotc.core.TypeErasure import dotty.tools.dotc.core.Constants.Constant @@ -508,7 +508,7 @@ object Splicer { // Take the flatten name of the class and the full package name val pack = tpe.classSymbol.topLevelClass.owner val packageName = if (pack == defn.EmptyPackageClass) "" else s"${pack.fullName}." - packageName + tpe.classSymbol.fullNameSeparated(FlatName).toString + packageName + tpe.classSymbol.fullNameSeparated(PrefixSeparator.Flat).toString } val sym = param.classSymbol diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index e97aa061c3f5..8f5a3ff1f359 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2483,7 +2483,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler else None def name: String = self.denot.name.toString - def fullName: String = self.denot.fullName.toString + def fullName: String = self.denot.tastyCompatibleFullName.toString def pos: Option[Position] = if self.exists then Some(self.sourcePos) else None diff --git a/tests/neg/symbolic-packages/App.scala b/tests/neg/symbolic-packages/App.scala new file mode 100644 index 000000000000..0bb6ad0cb216 --- /dev/null +++ b/tests/neg/symbolic-packages/App.scala @@ -0,0 +1,6 @@ +package example + +class Example: + + def foo: symbolic_>> = ??? // error: No type found + def bar: _root_.symbolic_>> = ??? // ok - will "mock superclass" diff --git a/tests/neg/symbolic-packages/Lib.scala b/tests/neg/symbolic-packages/Lib.scala new file mode 100644 index 000000000000..547577747a97 --- /dev/null +++ b/tests/neg/symbolic-packages/Lib.scala @@ -0,0 +1,3 @@ +package symbolic_>> + +class |+| diff --git a/tests/pos/symbolic-packages-dep/App_2.scala b/tests/pos/symbolic-packages-dep/App_2.scala new file mode 100644 index 000000000000..9b31d0053b2f --- /dev/null +++ b/tests/pos/symbolic-packages-dep/App_2.scala @@ -0,0 +1,6 @@ +package example + +class Example: + + def inferred = new fully_+.symbolic_>>.package_*.|+|() + def explicit: fully_+.symbolic_>>.package_*.|+| = new fully_+.symbolic_>>.package_*.|+|() diff --git a/tests/pos/symbolic-packages-dep/Lib_1.scala b/tests/pos/symbolic-packages-dep/Lib_1.scala new file mode 100644 index 000000000000..e8bc025302b8 --- /dev/null +++ b/tests/pos/symbolic-packages-dep/Lib_1.scala @@ -0,0 +1,3 @@ +package fully_+.symbolic_>>.package_* + +class |+| diff --git a/tests/pos/symbolic-packages/App.scala b/tests/pos/symbolic-packages/App.scala new file mode 100644 index 000000000000..9b31d0053b2f --- /dev/null +++ b/tests/pos/symbolic-packages/App.scala @@ -0,0 +1,6 @@ +package example + +class Example: + + def inferred = new fully_+.symbolic_>>.package_*.|+|() + def explicit: fully_+.symbolic_>>.package_*.|+| = new fully_+.symbolic_>>.package_*.|+|() diff --git a/tests/pos/symbolic-packages/Lib.scala b/tests/pos/symbolic-packages/Lib.scala new file mode 100644 index 000000000000..e8bc025302b8 --- /dev/null +++ b/tests/pos/symbolic-packages/Lib.scala @@ -0,0 +1,3 @@ +package fully_+.symbolic_>>.package_* + +class |+| diff --git a/tests/run-macros/symbolic-packages.check b/tests/run-macros/symbolic-packages.check new file mode 100644 index 000000000000..deb0ecf7b59c --- /dev/null +++ b/tests/run-macros/symbolic-packages.check @@ -0,0 +1,2 @@ +fully_$plus.symbolic_$greater$greater.package_$times.|+| +fully_+.symbolic_>>.package_*.|+| diff --git a/tests/run-macros/symbolic-packages/Lib_2.scala b/tests/run-macros/symbolic-packages/Lib_2.scala new file mode 100644 index 000000000000..e8bc025302b8 --- /dev/null +++ b/tests/run-macros/symbolic-packages/Lib_2.scala @@ -0,0 +1,3 @@ +package fully_+.symbolic_>>.package_* + +class |+| diff --git a/tests/run-macros/symbolic-packages/Test_2.scala b/tests/run-macros/symbolic-packages/Test_2.scala new file mode 100644 index 000000000000..03fb60b913bb --- /dev/null +++ b/tests/run-macros/symbolic-packages/Test_2.scala @@ -0,0 +1,4 @@ +@main def Test = + type Queried = fully_+.symbolic_>>.package_*.|+| + println(getFullName[Queried](decoded = false)) + println(getFullName[Queried](decoded = true)) diff --git a/tests/run-macros/symbolic-packages/macro_1.scala b/tests/run-macros/symbolic-packages/macro_1.scala new file mode 100644 index 000000000000..0289f9d4d2bc --- /dev/null +++ b/tests/run-macros/symbolic-packages/macro_1.scala @@ -0,0 +1,27 @@ +import scala.quoted.* +import scala.reflect.NameTransformer + +inline def getFullName[T](inline decoded: Boolean) = ${ Macro.getFullName[T]('decoded) } + +object Macro: + def getFullName[T: Type](decodedExpr: Expr[Boolean])(using Quotes): Expr[String] = + import quotes.reflect.* + + val decode = decodedExpr.valueOrAbort + + val tpe = TypeRepr.of[T] + + val cls = tpe.classSymbol match + case Some(cls) => cls + case _ => + report.error("Not a class") + return '{""} + + val name = cls.fullName + + if decode then + val parts = name.split('.').toSeq + val decoded = (parts.init.map(NameTransformer.decode) :+ parts.last).mkString(".") + Expr(decoded) + else + Expr(name)