diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 0b11ce90d94a..a293b0e9c1bc 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -342,7 +342,7 @@ object Trees { val point = span.point if (rawMods.is(Synthetic) || name.toTermName == nme.ERROR) Span(point) else { - val realName = name.stripModuleClassSuffix.lastPart.toString + val realName = name.stripModuleClassSuffix.lastPart Span(point, point + realName.length, point) } } diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 785dadce8006..8bea7b2c3c2d 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1172,8 +1172,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { // convert a numeric with a toXXX method def primitiveConversion(tree: Tree, numericCls: Symbol)(using Context): Tree = { - val mname = ("to" + numericCls.name).toTermName - val conversion = tree.tpe member mname + val mname = "to".concat(numericCls.name) + val conversion = tree.tpe member(mname) if (conversion.symbol.exists) tree.select(conversion.symbol.termRef).ensureApplied else if (tree.tpe.widen isRef numericCls) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 4335f01cc1b2..28cfe0d0a4f7 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -918,7 +918,12 @@ object Contexts { private[Contexts] val comparers = new mutable.ArrayBuffer[TypeComparer] private[Contexts] var comparersInUse: Int = 0 - private[dotc] var nameCharBuffer = new Array[Char](256) + private var charArray = new Array[Char](256) + + def sharedCharArray(len: Int): Array[Char] = + while len > charArray.length do + charArray = new Array[Char](charArray.length * 2) + charArray def reset(): Unit = { for ((_, set) <- uniqueSets) set.clear() diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index 857b78115fbb..630776890a5c 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -39,16 +39,25 @@ object Decorators { * This avoids some allocation relative to `termName(s)` */ def sliceToTermName(start: Int, end: Int)(using Context): SimpleName = - val base = ctx.base val len = end - start - while len > base.nameCharBuffer.length do - base.nameCharBuffer = new Array[Char](base.nameCharBuffer.length * 2) - s.getChars(start, end, base.nameCharBuffer, 0) - termName(base.nameCharBuffer, 0, len) + val chars = ctx.base.sharedCharArray(len) + s.getChars(start, end, chars, 0) + termName(chars, 0, len) def sliceToTypeName(start: Int, end: Int)(using Context): TypeName = sliceToTermName(start, end).toTypeName + def concat(name: Name)(using Context): SimpleName = name match + case name: SimpleName => + val len = s.length + name.length + var chars = ctx.base.sharedCharArray(len) + s.getChars(0, s.length, chars, 0) + if name.length != 0 then name.getChars(0, name.length, chars, s.length) + termName(chars, 0, len) + case name: TypeName => s.concat(name.toTermName) + case _ => termName(s.concat(name.toString)) + end extension + /** Implements a findSymbol method on iterators of Symbols that * works like find but avoids Option, replacing None with NoSymbol. */ diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index 6e5de7071aa7..4185c5171089 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -7,6 +7,7 @@ import NameOps._ import StdNames._ import NameTags._ import Contexts._ +import Decorators._ import collection.mutable import scala.annotation.internal.sharable @@ -208,6 +209,8 @@ object NameKinds { extends NumberedNameKind(UNIQUE, s"Unique $separator") { override def definesNewName: Boolean = true + val separatorName = separator.toTermName + def mkString(underlying: TermName, info: ThisInfo): String = { val safePrefix = str.sanitize(underlying.toString) + separator safePrefix + info.num @@ -226,10 +229,10 @@ object NameKinds { /** An extractor for unique names of arbitrary kind */ object AnyUniqueName { - def unapply(name: DerivedName): Option[(TermName, String, Int)] = name match { + def unapply(name: DerivedName): Option[(TermName, TermName, Int)] = name match { case DerivedName(qual, info: NumberedInfo) => info.kind match { - case unique: UniqueNameKind => Some((qual, unique.separator, info.num)) + case unique: UniqueNameKind => Some((qual, unique.separatorName, info.num)) case _ => None } case _ => None diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index dce8350db24a..941b0d9c5482 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -8,6 +8,7 @@ import scala.internal.Chars import Chars.isOperatorPart import Definitions._ import nme._ +import Decorators.concat object NameOps { @@ -138,7 +139,7 @@ object NameOps { case _ => false /** Add an `extension_` in front of this name */ - def toExtensionName = termName("extension_" ++ name.toString) + def toExtensionName(using Context): SimpleName = "extension_".concat(name) /** Drop `extension_` in front of this name, if it has this prefix */ def dropExtension = name match diff --git a/compiler/src/dotty/tools/dotc/core/Names.scala b/compiler/src/dotty/tools/dotc/core/Names.scala index 1f85070cbcc5..61196f245dc8 100644 --- a/compiler/src/dotty/tools/dotc/core/Names.scala +++ b/compiler/src/dotty/tools/dotc/core/Names.scala @@ -337,6 +337,13 @@ object Names { def head: Char = apply(0) def last: Char = apply(length - 1) + /** Copy character slice (from until end) to character array starting at `dstStart`. + * @pre Destination must have enough space to hold all characters of this name. + */ + def getChars(from: Int, end: Int, dst: Array[Char], dstStart: Int): Unit = + assert(0 <= from && from <= end && end <= length) + Array.copy(chrs, start + from, dst, dstStart, end - from) + override def asSimpleName: SimpleName = this override def toSimpleName: SimpleName = this override final def mangle: SimpleName = encode diff --git a/compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala b/compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala index 63b724b051f8..1294b6fe0a72 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala @@ -35,7 +35,7 @@ class NameBuffer extends TastyBuffer(10000) { case AnyQualifiedName(prefix, name) => nameIndex(prefix); nameIndex(name) case AnyUniqueName(original, separator, num) => - nameIndex(separator.toTermName) + nameIndex(separator) if (!original.isEmpty) nameIndex(original) case DerivedName(original, _) => nameIndex(original) @@ -82,7 +82,7 @@ class NameBuffer extends TastyBuffer(10000) { withLength { writeNameRef(prefix); writeNameRef(name) } case AnyUniqueName(original, separator, num) => withLength { - writeNameRef(separator.toTermName) + writeNameRef(separator) writeNat(num) if (!original.isEmpty) writeNameRef(original) } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala index ddaf0c398891..2212c1842e0a 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala @@ -46,7 +46,11 @@ class PositionPickler( lastIndex = index lastSpan = span - pickledIndices += index + pickledIndices.addOne(index) + // Note `+=` boxes since it is a generic @inline function in `SetOps` + // that forwards to the specialized `addOne` in `BitSet`. Since the + // current backend does not implement `@inline` we are missing the + // specialization. } def pickleSource(source: SourceFile): Unit = { diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index b37d933ab7be..241319ea6ff7 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -7,7 +7,7 @@ import core.StdNames._, core.Comments._ import util.SourceFile import java.lang.Character.isDigit import scala.internal.Chars._ -import util.SourcePosition +import util.{SourcePosition, CharBuffer} import util.Spans.Span import config.Config import config.Printers.lexical @@ -21,38 +21,6 @@ import config.Feature.migrateTo3 import config.SourceVersion._ import reporting.Message -object Cbufs { - import java.lang.StringBuilder - - private final val TargetCapacity = 256 - - opaque type Cbuf = StringBuilder - object Cbuf: - def apply(): Cbuf = new StringBuilder(TargetCapacity) - - extension (buf: Cbuf): - def clear(): Unit = { - if buf.capacity() > TargetCapacity then - buf.setLength(TargetCapacity) - buf.trimToSize() - end if - buf.setLength(0) - } - def toCharArray: Array[Char] = { - val n = buf.length() - val res = new Array[Char](n) - buf.getChars(0, n, res, 0) - res - } - def append(c: Char): buf.type = { buf.append(c) ; buf } - def isEmpty: Boolean = buf.length() == 0 - def length: Int = buf.length() - def last: Char = buf.charAt(buf.length() - 1) - end extension -} - -import Cbufs._ - object Scanners { /** Offset into source character array */ @@ -142,22 +110,16 @@ object Scanners { /** A character buffer for literals */ - protected val litBuf = Cbuf() + protected val litBuf = CharBuffer() /** append Unicode character to "litBuf" buffer */ protected def putChar(c: Char): Unit = litBuf.append(c) - /** Return buffer contents and clear */ - def flushBuf(buf: Cbuf): String = { - val str = buf.toString - buf.clear() - str - } - /** Clear buffer and set name and token */ def finishNamed(idtoken: Token = IDENTIFIER, target: TokenData = this): Unit = { - target.name = termName(flushBuf(litBuf)) + target.name = termName(litBuf.chars, 0, litBuf.length) + litBuf.clear() target.token = idtoken if (idtoken == IDENTIFIER) target.token = toToken(target.name) @@ -168,7 +130,8 @@ object Scanners { /** Clear buffer and set string */ def setStrVal(): Unit = - strVal = flushBuf(litBuf) + strVal = litBuf.toString + litBuf.clear() @inline def isNumberSeparator(c: Char): Boolean = c == '_' @@ -241,7 +204,7 @@ object Scanners { def getDocComment(pos: Int): Option[Comment] = docstringMap.get(pos) /** A buffer for comments */ - private val commentBuf = Cbuf() + private val commentBuf = CharBuffer() private def handleMigration(keyword: Token): Token = if keyword == ERASED && !ctx.settings.YerasedTerms.value then IDENTIFIER @@ -888,7 +851,8 @@ object Scanners { def finishComment(): Boolean = { if (keepComments) { val pos = Span(start, charOffset - 1, start) - val comment = Comment(pos, flushBuf(commentBuf)) + val comment = Comment(pos, commentBuf.toString) + commentBuf.clear() commentPosBuf += pos if (comment.isDocComment) diff --git a/compiler/src/dotty/tools/dotc/transform/ElimOpaque.scala b/compiler/src/dotty/tools/dotc/transform/ElimOpaque.scala index ea5b53483e0f..55ad79dbe3e9 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimOpaque.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimOpaque.scala @@ -66,7 +66,7 @@ class ElimOpaque extends MiniPhase with DenotTransformer { if sym == defn.Any_== || sym == defn.Any_!= then tree match case Apply(Select(receiver, name: TermName), args) - if atPhase(thisPhase)(receiver.tpe.widen.typeSymbol.isOpaqueAlias) => + if atPhase(thisPhase)(receiver.tpe.widen.dealias.typeSymbol.isOpaqueAlias) => applyOverloaded(receiver, name, args, Nil, defn.BooleanType) case _ => tree diff --git a/compiler/src/dotty/tools/dotc/typer/Deriving.scala b/compiler/src/dotty/tools/dotc/typer/Deriving.scala index 969aa82f3af8..7eadcf2114a5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Deriving.scala +++ b/compiler/src/dotty/tools/dotc/typer/Deriving.scala @@ -47,7 +47,7 @@ trait Deriving { * @param reportErrors Report an error if an instance with the same name exists already */ private def addDerivedInstance(clsName: Name, info: Type, pos: SrcPos): Unit = { - val instanceName = s"derived$$$clsName".toTermName + val instanceName = "derived$".concat(clsName) if (ctx.denotNamed(instanceName).exists) report.error(i"duplicate type class derivation for $clsName", pos) else diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 78c9267d8700..0f33a950d2c0 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -137,7 +137,7 @@ object ProtoTypes { extends CachedProxyType with ProtoType with ValueTypeOrProto { private var myExtensionName: TermName = null - def extensionName: TermName = + def extensionName(using Context): TermName = if myExtensionName == null then myExtensionName = name.toExtensionName myExtensionName diff --git a/compiler/src/dotty/tools/dotc/util/CharBuffer.scala b/compiler/src/dotty/tools/dotc/util/CharBuffer.scala new file mode 100644 index 000000000000..ad51702ca7f1 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/util/CharBuffer.scala @@ -0,0 +1,28 @@ +package dotty.tools +package dotc +package util + +/** A character buffer that exposes the internal array for reading. + * That way we can avoid copying when converting to names. + */ +class CharBuffer(initialSize: Int = 1024): + private var cs: Array[Char] = new Array[Char](initialSize) + private var len: Int = 0 + + def append(ch: Char): Unit = + if len == cs.length then + val cs1 = new Array[Char](len * 2) + Array.copy(cs, 0, cs1, 0, len) + cs = cs1 + cs(len) = ch + len += 1 + + def chars = cs + def length = len + def isEmpty: Boolean = len == 0 + def last: Char = cs(len - 1) + def clear(): Unit = len = 0 + + override def toString = String(cs, 0, len) + +