Skip to content

Commit a64725c

Browse files
authored
Merge pull request #7601 from dotty-staging/fix-encoding-2
Revert to the Scala 2 name encoding scheme
2 parents 8c48334 + 4bc3241 commit a64725c

File tree

15 files changed

+135
-115
lines changed

15 files changed

+135
-115
lines changed

compiler/src/dotty/tools/backend/sjs/JSEncoding.scala

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -229,10 +229,7 @@ object JSEncoding {
229229
fullyMangledString(sym.name)
230230
}
231231

232-
/** Work around https://github.com/lampepfl/dotty/issues/5936 by bridging
233-
* most (all?) of the gap in encoding so that Dotty.js artifacts are
234-
* compatible with the restrictions on valid IR identifier names.
235-
*/
232+
/** Convert Dotty mangled names into valid IR identifier names. */
236233
private def fullyMangledString(name: Name): String = {
237234
val base = name.mangledString
238235
val len = base.length
@@ -245,10 +242,8 @@ object JSEncoding {
245242
val c = base.charAt(i)
246243
if (c == '_')
247244
result.append("$und")
248-
else if (Character.isJavaIdentifierPart(c) || c == '.')
249-
result.append(c)
250245
else
251-
result.append("$u%04x".format(c.toInt))
246+
result.append(c)
252247
i += 1
253248
}
254249
result.toString()
@@ -257,7 +252,7 @@ object JSEncoding {
257252
var i = 0
258253
while (i != len) {
259254
val c = base.charAt(i)
260-
if (c == '_' || !Character.isJavaIdentifierPart(c))
255+
if (c == '_')
261256
return encodeFurther()
262257
i += 1
263258
}

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import Decorators._, transform.SymUtils._
99
import NameKinds.{UniqueName, EvidenceParamName, DefaultGetterName}
1010
import typer.{FrontEnd, Namer}
1111
import util.{Property, SourceFile, SourcePosition}
12-
import util.NameTransformer.avoidIllegalChars
1312
import collection.mutable.ListBuffer
1413
import reporting.diagnostic.messages._
1514
import reporting.trace
@@ -935,7 +934,7 @@ object desugar {
935934

936935
/** Invent a name for an anonympus given of type or template `impl`. */
937936
def inventGivenName(impl: Tree)(implicit ctx: Context): SimpleName =
938-
avoidIllegalChars(s"given_${inventName(impl)}".toTermName.asSimpleName)
937+
s"given_${inventName(impl)}".toTermName.asSimpleName
939938

940939
/** The normalized name of `mdef`. This means
941940
* 1. Check that the name does not redefine a Scala core class.
@@ -1250,7 +1249,7 @@ object desugar {
12501249
else {
12511250
var fileName = ctx.source.file.name
12521251
val sourceName = fileName.take(fileName.lastIndexOf('.'))
1253-
val groupName = avoidIllegalChars((sourceName ++ str.TOPLEVEL_SUFFIX).toTermName.asSimpleName)
1252+
val groupName = (sourceName ++ str.TOPLEVEL_SUFFIX).toTermName.asSimpleName
12541253
val grouped = ModuleDef(groupName, Template(emptyConstructor, Nil, Nil, EmptyValDef, nestedStats))
12551254
cpy.PackageDef(pdef)(pdef.pid, topStats :+ grouped)
12561255
}

compiler/src/dotty/tools/dotc/core/Names.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,8 @@ object Names {
147147
/** Is this name empty? */
148148
def isEmpty: Boolean
149149

150-
/** Does (the first part of) this name start with `str`? */
151-
def startsWith(str: String): Boolean = firstPart.startsWith(str)
150+
/** Does (the first part of) this name starting at index `start` starts with `str`? */
151+
def startsWith(str: String, start: Int = 0): Boolean = firstPart.startsWith(str, start)
152152

153153
/** Does (the last part of) this name end with `str`? */
154154
def endsWith(str: String): Boolean = lastPart.endsWith(str)
@@ -362,9 +362,9 @@ object Names {
362362

363363
override def isEmpty: Boolean = length == 0
364364

365-
override def startsWith(str: String): Boolean = {
365+
override def startsWith(str: String, start: Int): Boolean = {
366366
var i = 0
367-
while (i < str.length && i < length && apply(i) == str(i)) i += 1
367+
while (i < str.length && start + i < length && apply(start + i) == str(i)) i += 1
368368
i == str.length
369369
}
370370

compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,7 @@ class ClassfileParser(
686686
for (entry <- innerClasses.values) {
687687
// create a new class member for immediate inner classes
688688
if (entry.outerName == currentClassName) {
689-
val file = ctx.platform.classPath.findClassFile(entry.externalName.mangledString) getOrElse {
689+
val file = ctx.platform.classPath.findClassFile(entry.externalName.toString) getOrElse {
690690
throw new AssertionError(entry.externalName)
691691
}
692692
enterClassAndModule(entry, file, entry.jflags)

compiler/src/dotty/tools/dotc/parsing/Scanners.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import core.StdNames._, core.Comments._
77
import util.SourceFile
88
import java.lang.Character.isDigit
99
import scala.internal.Chars._
10-
import util.NameTransformer.avoidIllegalChars
1110
import util.Spans.Span
1211
import config.Config
1312
import config.Printers.lexical
@@ -929,7 +928,6 @@ object Scanners {
929928
if (ch == '`') {
930929
nextChar()
931930
finishNamed(BACKQUOTED_IDENT)
932-
name = avoidIllegalChars(name)
933931
if (name.length == 0)
934932
error("empty quoted identifier")
935933
else if (name == nme.WILDCARD)

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
7777
}
7878

7979
override def nameString(name: Name): String =
80-
if (ctx.settings.YdebugNames.value) name.debugString else NameTransformer.decodeIllegalChars(name.toString)
80+
if (ctx.settings.YdebugNames.value) name.debugString else name.toString
8181

8282
override protected def simpleNameString(sym: Symbol): String =
8383
nameString(if (ctx.property(XprintMode).isEmpty) sym.initial.name else sym.name)

compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,10 @@ object GenericSignatures {
103103
jsig(finalType)
104104
}
105105

106-
// This will reject any name that has characters that cannot appear in
107-
// names on the JVM. Interop with Java is not guaranteed for those, so we
108-
// dont need to generate signatures for them.
109-
def sanitizeName(name: Name): String = {
110-
val nameString = name.mangledString
111-
if (nameString.forall(c => c == '.' || Character.isJavaIdentifierPart(c)))
112-
nameString
113-
else
114-
throw new UnknownSig
115-
}
106+
// This works as long as mangled names are always valid valid Java identifiers,
107+
// if we change our name encoding, we'll have to `throw new UnknownSig` here for
108+
// names which are not valid Java identifiers (see git history of this method).
109+
def sanitizeName(name: Name): String = name.mangledString
116110

117111
// Anything which could conceivably be a module (i.e. isn't known to be
118112
// a type parameter or similar) must go through here or the signature is

compiler/src/dotty/tools/dotc/util/NameTransformer.scala

Lines changed: 97 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@ import scala.annotation.internal.sharable
1313
object NameTransformer {
1414

1515
private val nops = 128
16+
private val ncodes = 26 * 26
1617

17-
@sharable private val op2code = new Array[String](nops)
18-
@sharable private val str2op = new mutable.HashMap[String, Char]
18+
private class OpCodes(val op: Char, val code: String, val next: OpCodes)
1919

20+
@sharable private val op2code = new Array[String](nops)
21+
@sharable private val code2op = new Array[OpCodes](ncodes)
2022
private def enterOp(op: Char, code: String) = {
21-
op2code(op) = code
22-
str2op(code) = op
23+
op2code(op.toInt) = code
24+
val c = (code.charAt(1) - 'a') * 26 + code.charAt(2) - 'a'
25+
code2op(c.toInt) = new OpCodes(op, code, code2op(c))
2326
}
2427

2528
/* Note: decoding assumes opcodes are only ever lowercase. */
@@ -42,99 +45,107 @@ object NameTransformer {
4245
enterOp('?', "$qmark")
4346
enterOp('@', "$at")
4447

45-
/** Expand characters that are illegal as JVM method names by `$u`, followed
46-
* by the character's unicode expansion.
47-
*/
48-
def avoidIllegalChars(name: SimpleName): SimpleName = {
49-
var i = name.length - 1
50-
while (i >= 0 && Chars.isValidJVMMethodChar(name(i))) i -= 1
51-
if (i >= 0)
52-
termName(
53-
name.toString.flatMap(ch =>
54-
if (Chars.isValidJVMMethodChar(ch)) ch.toString else "$u%04X".format(ch.toInt)))
55-
else name
56-
}
57-
58-
/** Decode expanded characters starting with `$u`, followed by the character's unicode expansion. */
59-
def decodeIllegalChars(name: String): String =
60-
if (name.contains("$u")) {
61-
val sb = new mutable.StringBuilder()
62-
var i = 0
63-
while (i < name.length)
64-
if (i < name.length - 5 && name(i) == '$' && name(i + 1) == 'u') {
65-
val numbers = name.substring(i + 2, i + 6)
66-
try sb.append(Integer.valueOf(name.substring(i + 2, i + 6), 16).toChar)
67-
catch {
68-
case _: java.lang.NumberFormatException =>
69-
sb.append("$u").append(numbers)
70-
}
71-
i += 6
72-
}
73-
else {
74-
sb.append(name(i))
75-
i += 1
76-
}
77-
sb.result()
78-
}
79-
else name
80-
81-
/** Replace operator symbols by corresponding expansion strings.
82-
*
83-
* @param name the string to encode
84-
* @return the string with all recognized opchars replaced with their encoding
85-
*
86-
* Operator symbols are only recognized if they make up the whole name, or
87-
* if they make up the last part of the name which follows a `_`.
48+
/** Replace operator symbols by corresponding expansion strings, and replace
49+
* characters that are not valid Java identifiers by "$u" followed by the
50+
* character's unicode expansion.
51+
* Note that no attempt is made to escape the use of '$' in `name`: blindly
52+
* escaping them might make it impossible to call some platform APIs. This
53+
* unfortunately means that `decode(encode(name))` might not be equal to
54+
* `name`, this is considered acceptable since '$' is a reserved character in
55+
* the Scala spec as well as the Java spec.
8856
*/
8957
def encode(name: SimpleName): SimpleName = {
90-
def loop(len: Int, ops: List[String]): SimpleName = {
91-
def convert =
92-
if (ops.isEmpty) name
93-
else {
94-
val buf = new java.lang.StringBuilder
95-
buf.append(chrs, name.start, len)
96-
for (op <- ops) buf.append(op)
97-
termName(buf.toString)
58+
var buf: StringBuilder = null
59+
val len = name.length
60+
var i = 0
61+
while (i < len) {
62+
val c = name(i)
63+
if (c < nops && (op2code(c.toInt) ne null)) {
64+
if (buf eq null) {
65+
buf = new StringBuilder()
66+
buf.append(name.sliceToString(0, i))
67+
}
68+
buf.append(op2code(c.toInt))
69+
/* Handle glyphs that are not valid Java/JVM identifiers */
70+
}
71+
else if (!Character.isJavaIdentifierPart(c)) {
72+
if (buf eq null) {
73+
buf = new StringBuilder()
74+
buf.append(name.sliceToString(0, i))
9875
}
99-
if (len == 0 || name(len - 1) == '_') convert
100-
else {
101-
val ch = name(len - 1)
102-
if (ch <= nops && op2code(ch) != null)
103-
loop(len - 1, op2code(ch) :: ops)
104-
else if (Chars.isSpecial(ch))
105-
loop(len - 1, ch.toString :: ops)
106-
else name
76+
buf.append("$u%04X".format(c.toInt))
10777
}
78+
else if (buf ne null) {
79+
buf.append(c)
80+
}
81+
i += 1
10882
}
109-
loop(name.length, Nil)
83+
if (buf eq null) name else termName(buf.toString)
11084
}
11185

112-
/** Replace operator expansions by the operators themselves.
113-
* Operator expansions are only recognized if they make up the whole name, or
114-
* if they make up the last part of the name which follows a `_`.
86+
/** Replace operator expansions by the operators themselves,
87+
* and decode `$u....` expansions into unicode characters.
11588
*/
11689
def decode(name: SimpleName): SimpleName = {
117-
def loop(len: Int, ops: List[Char]): SimpleName = {
118-
def convert =
119-
if (ops.isEmpty) name
120-
else {
121-
val buf = new java.lang.StringBuilder
122-
buf.append(chrs, name.start, len)
123-
for (op <- ops) buf.append(op)
124-
termName(buf.toString)
125-
}
126-
if (len == 0 || name(len - 1) == '_') convert
127-
else if (Chars.isSpecial(name(len - 1))) loop(len - 1, name(len - 1) :: ops)
128-
else {
129-
val idx = name.lastIndexOf('$', len - 1)
130-
if (idx >= 0 && idx + 2 < len)
131-
str2op.get(name.sliceToString(idx, len)) match {
132-
case Some(ch) => loop(idx, ch :: ops)
133-
case None => name
90+
//System.out.println("decode: " + name);//DEBUG
91+
var buf: StringBuilder = null
92+
val len = name.length
93+
var i = 0
94+
while (i < len) {
95+
var ops: OpCodes = null
96+
var unicode = false
97+
val c = name(i)
98+
if (c == '$' && i + 2 < len) {
99+
val ch1 = name(i + 1)
100+
if ('a' <= ch1 && ch1 <= 'z') {
101+
val ch2 = name(i + 2)
102+
if ('a' <= ch2 && ch2 <= 'z') {
103+
ops = code2op((ch1 - 'a') * 26 + ch2 - 'a')
104+
while ((ops ne null) && !name.startsWith(ops.code, i)) ops = ops.next
105+
if (ops ne null) {
106+
if (buf eq null) {
107+
buf = new StringBuilder()
108+
buf.append(name.sliceToString(0, i))
109+
}
110+
buf.append(ops.op)
111+
i += ops.code.length()
112+
}
113+
/* Handle the decoding of Unicode glyphs that are
114+
* not valid Java/JVM identifiers */
115+
} else if ((len - i) >= 6 && // Check that there are enough characters left
116+
ch1 == 'u' &&
117+
((Character.isDigit(ch2)) ||
118+
('A' <= ch2 && ch2 <= 'F'))) {
119+
/* Skip past "$u", next four should be hexadecimal */
120+
val hex = name.sliceToString(i+2, i+6)
121+
try {
122+
val str = Integer.parseInt(hex, 16).toChar
123+
if (buf eq null) {
124+
buf = new StringBuilder()
125+
buf.append(name.sliceToString(0, i))
126+
}
127+
buf.append(str)
128+
/* 2 for "$u", 4 for hexadecimal number */
129+
i += 6
130+
unicode = true
131+
} catch {
132+
case _:NumberFormatException =>
133+
/* `hex` did not decode to a hexadecimal number, so
134+
* do nothing. */
135+
}
134136
}
135-
else name
137+
}
138+
}
139+
/* If we didn't see an opcode or encoded Unicode glyph, and the
140+
buffer is non-empty, write the current character and advance
141+
one */
142+
if ((ops eq null) && !unicode) {
143+
if (buf ne null)
144+
buf.append(c)
145+
i += 1
136146
}
137147
}
138-
loop(name.length, Nil)
148+
//System.out.println("= " + (if (buf == null) name else buf.toString()));//DEBUG
149+
if (buf eq null) name else termName(buf.toString)
139150
}
140151
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
scalaVersion := sys.props("plugin.scalaVersion")
2+
3+
libraryDependencies ++= {
4+
Seq(
5+
("com.typesafe.akka" %% "akka-http" % "10.1.10"),
6+
("com.typesafe.akka" %% "akka-stream" % "2.6.0")
7+
).map(_.withDottyCompat(scalaVersion.value))
8+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import akka.actor.ActorSystem
2+
import akka.http.scaladsl.Http
3+
import akka.http.scaladsl.model._
4+
import akka.http.scaladsl.server.Directives._
5+
import akka.stream.ActorMaterializer
6+
import scala.io.StdIn
7+
8+
object WebServer {
9+
def main(args: Array[String]): Unit = {
10+
val x = ContentTypes.`text/html(UTF-8)`
11+
}
12+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % sys.props("plugin.version"))
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
> compile

tests/generic-java-signatures/invalidNames.check

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
$bang$u005B$u005D$colon$u003B$bang$bang <: java.util.Date

0 commit comments

Comments
 (0)