aboutsummaryrefslogtreecommitdiff
path: root/compiler/src/dotty/tools/dotc/core/NameOps.scala
blob: 031cda1bd380240c26502af7d65a3854f912d379 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
package dotty.tools.dotc
package core

import java.security.MessageDigest
import scala.annotation.switch
import scala.io.Codec
import Names._, StdNames._, Contexts._, Symbols._, Flags._, NameExtractors._
import Decorators.PreNamedString
import util.{Chars, NameTransformer}
import Chars.isOperatorPart
import Definitions._
import config.Config

object NameOps {

  final object compactify {
    lazy val md5 = MessageDigest.getInstance("MD5")

    /** COMPACTIFY
     *
     *  The hashed name has the form (prefix + marker + md5 + marker + suffix), where
     *   - prefix/suffix.length = MaxNameLength / 4
     *   - md5.length = 32
     *
     *  We obtain the formula:
     *
     *   FileNameLength = 2*(MaxNameLength / 4) + 2.marker.length + 32 + 6
     *
     *  (+6 for ".class"). MaxNameLength can therefore be computed as follows:
     */
    def apply(s: String)(implicit ctx: Context): String = {
      val marker = "$$$$"
      val limit: Int = ctx.settings.maxClassfileName.value
      val MaxNameLength = (limit - 6) min 2 * (limit - 6 - 2 * marker.length - 32)

      def toMD5(s: String, edge: Int): String = {
        val prefix = s take edge
        val suffix = s takeRight edge

        val cs = s.toArray
        val bytes = Codec toUTF8 cs
        md5 update bytes
        val md5chars = (md5.digest() map (b => (b & 0xFF).toHexString)).mkString

        prefix + marker + md5chars + marker + suffix
      }

      if (s.length <= MaxNameLength) s else toMD5(s, MaxNameLength / 4)
    }
  }

  implicit class NameDecorator[N <: Name](val name: N) extends AnyVal {
    import nme._

    def likeTyped(n: PreName): N =
      (if (name.isTermName) n.toTermName else n.toTypeName).asInstanceOf[N]

    def isConstructorName = name == CONSTRUCTOR || name == TRAIT_CONSTRUCTOR
    def isStaticConstructorName = name == STATIC_CONSTRUCTOR
    def isImplClassName = name endsWith IMPL_CLASS_SUFFIX
    def isLocalDummyName = name startsWith LOCALDUMMY_PREFIX
    def isLoopHeaderLabel = (name startsWith WHILE_PREFIX) || (name startsWith DO_WHILE_PREFIX)
    def isProtectedAccessorName = name startsWith PROTECTED_PREFIX
    def isReplWrapperName = name.toSimpleName containsSlice INTERPRETER_IMPORT_WRAPPER
    def isSetterName = name endsWith SETTER_SUFFIX
    def isSingletonName = name endsWith SINGLETON_SUFFIX
    def isImportName = name startsWith IMPORT
    def isFieldName = name endsWith LOCAL_SUFFIX
    def isDefaultGetterName = name.isTermName && name.asTermName.defaultGetterIndex >= 0
    def isScala2LocalSuffix = name.endsWith(" ")
    def isModuleVarName(name: Name): Boolean =
      name.stripAnonNumberSuffix endsWith MODULE_VAR_SUFFIX
    def isSelectorName = name.startsWith(" ") && name.tail.forall(_.isDigit)
    def isLazyLocal = name.endsWith(nme.LAZY_LOCAL)
    def isOuterSelect = name.endsWith(nme.OUTER_SELECT)
    def isInlineAccessor = name.startsWith(nme.INLINE_ACCESSOR_PREFIX)

    /** Is name a variable name? */
    def isVariableName: Boolean = name.length > 0 && {
      val first = name.head
      (((first.isLower && first.isLetter) || first == '_')
        && (name != false_)
        && (name != true_)
        && (name != null_))
    }

    def isOpAssignmentName: Boolean = name match {
      case raw.NE | raw.LE | raw.GE | EMPTY =>
        false
      case name: SimpleTermName =>
        name.length > 0 && name.last == '=' && name.head != '=' && isOperatorPart(name.head)
      case _ =>
        false
    }

    /** If the name ends with $nn where nn are
      * all digits, strip the $ and the digits.
      * Otherwise return the argument.
      */
    def stripAnonNumberSuffix: Name = {
      var pos = name.length
      while (pos > 0 && name(pos - 1).isDigit)
        pos -= 1

      if (pos > 0 && pos < name.length && name(pos - 1) == '$')
        name take (pos - 1)
      else
        name
    }

    /** Convert this module name to corresponding module class name */
    def moduleClassName: TypeName = name.derived(ModuleClassName).toTypeName

    /** Convert this module class name to corresponding source module name */
    def sourceModuleName: TermName = stripModuleClassSuffix.toTermName

    /** If name ends in module class suffix, drop it */
    def stripModuleClassSuffix: Name = name.exclude(ModuleClassName)

    /** If flags is a ModuleClass but not a Package, add module class suffix */
    def adjustIfModuleClass(flags: Flags.FlagSet): N = {
      if (flags is (ModuleClass, butNot = Package)) name.asTypeName.moduleClassName
      else likeTyped(name.toTermName.exclude(AvoidClashName))
    }.asInstanceOf[N]

    /** The superaccessor for method with given name */
    def superName: TermName = SuperAccessorName(name.toTermName)

    /** The expanded name of `name` relative to given class `base`.
     */
    def expandedName(base: Symbol, separator: Name)(implicit ctx: Context): N =
      expandedName(if (base.name.is(ExpandedName)) base.name else base.fullNameSeparated("$"), separator)

    def expandedName(base: Symbol)(implicit ctx: Context): N = expandedName(base, nme.EXPAND_SEPARATOR)

    /** The expanded name of `name` relative to `basename` with given `separator`
     */
    def expandedName(prefix: Name, separator: Name = nme.EXPAND_SEPARATOR): N =
      likeTyped {
        def qualify(name: SimpleTermName) =
          separatorToQualified(separator.toString)(prefix.toTermName, name)
        name rewrite {
          case name: SimpleTermName =>
            qualify(name)
          case AnyQualifiedName(_, _) =>
            // Note: an expanded name may itself be expanded. For example, look at javap of scala.App.initCode
            qualify(name.toSimpleName)
        }
      }

    def expandedName(prefix: Name): N = expandedName(prefix, nme.EXPAND_SEPARATOR)

    /** Revert the expanded name. */
    def unexpandedName: N = likeTyped {
      name.rewrite { case ExpandedName(_, unexp) => unexp }
    }

    def unexpandedNameOfMangled: N = likeTyped {
      var idx = name.lastIndexOfSlice(nme.EXPAND_SEPARATOR)

      // Hack to make super accessors from traits work. They would otherwise fail because of #765
      // TODO: drop this once we have more robust name handling
      if (idx > FalseSuperLength && name.slice(idx - FalseSuperLength, idx) == FalseSuper)
        idx -= FalseSuper.length

      if (idx < 0) name else (name drop (idx + nme.EXPAND_SEPARATOR.length))
    }

    def expandedPrefix: N = likeTyped { name.exclude(ExpandedName) }

    def expandedPrefixOfMangled: N = {
      val idx = name.lastIndexOfSlice(nme.EXPAND_SEPARATOR)
      assert(idx >= 0)
      likeTyped(name.take(idx))
    }

    def unmangleExpandedName: N =
      if (name.isSimple) {
        val unmangled = unexpandedNameOfMangled
        if (name eq unmangled) name
        else likeTyped(
          ExpandedName(expandedPrefixOfMangled.toTermName, unmangled.asSimpleName))
      }
      else name

    def implClassName: N = likeTyped(name ++ tpnme.IMPL_CLASS_SUFFIX)

    def errorName: N = likeTyped(name ++ nme.ERROR)

    def directName: N = likeTyped(name ++ DIRECT_SUFFIX)

    def freshened(implicit ctx: Context): N =
      likeTyped(
        if (name.is(ModuleClassName)) name.stripModuleClassSuffix.freshened.moduleClassName
        else likeTyped(ctx.freshName(name ++ NameTransformer.NAME_JOIN_STRING)))
/*
    /** Name with variance prefix: `+` for covariant, `-` for contravariant */
    def withVariance(v: Int): N =
      if (hasVariance) dropVariance.withVariance(v)
      else v match {
        case -1 => likeTyped('-' +: name)
        case  1 => likeTyped('+' +: name)
        case  0 => name
      }

    /** Does name have a `+`/`-` variance prefix? */
    def hasVariance: Boolean =
      name.nonEmpty && name.head == '+' || name.head == '-'

    /** Drop variance prefix if name has one */
    def dropVariance: N = if (hasVariance) likeTyped(name.tail) else name

    /** The variance as implied by the variance prefix, or 0 if there is
     *  no variance prefix.
     */
    def variance = name.head match {
      case '-' => -1
      case '+' => 1
      case _ => 0
    }

*/
    def unmangleClassName: N =
      if (name.isSimple && name.isTypeName)
        if (name.endsWith(MODULE_SUFFIX) && !tpnme.falseModuleClassNames.contains(name.asTypeName))
          likeTyped(name.dropRight(MODULE_SUFFIX.length).moduleClassName)
        else name
      else name

    /** Translate a name into a list of simple TypeNames and TermNames.
     *  In all segments before the last, type/term is determined by whether
     *  the following separator char is '.' or '#'.  The last segment
     *  is of the same type as the original name.
     *
     *  Examples:
     *
     *  package foo {
     *    object Lorax { object Wog ; class Wog }
     *    class Lorax  { object Zax ; class Zax }
     *  }
     *
     *  f("foo.Lorax".toTermName)  == List("foo": Term, "Lorax": Term) // object Lorax
     *  f("foo.Lorax".toTypeName)  == List("foo": Term, "Lorax": Type) // class Lorax
     *  f("Lorax.Wog".toTermName)  == List("Lorax": Term, "Wog": Term) // object Wog
     *  f("Lorax.Wog".toTypeName)  == List("Lorax": Term, "Wog": Type) // class Wog
     *  f("Lorax#Zax".toTermName)  == List("Lorax": Type, "Zax": Term) // object Zax
     *  f("Lorax#Zax".toTypeName)  == List("Lorax": Type, "Zax": Type) // class Zax
     *
     *  Note that in actual scala syntax you cannot refer to object Zax without an
     *  instance of Lorax, so Lorax#Zax could only mean the type.  One might think
     *  that Lorax#Zax.type would work, but this is not accepted by the parser.
     *  For the purposes of referencing that object, the syntax is allowed.
     */
    def segments: List[Name] = {
      def mkName(name: Name, follow: Char): Name =
        if (follow == '.') name.toTermName else name.toTypeName

      name.indexWhere(ch => ch == '.' || ch == '#') match {
        case -1 =>
          if (name.isEmpty) scala.Nil else name :: scala.Nil
        case idx =>
          mkName(name take idx, name(idx)) :: (name drop (idx + 1)).segments
      }
    }

    /** Is a synthetic function name
     *    - N for FunctionN
     *    - N for ImplicitFunctionN
      *   - (-1) otherwise
     */
    def functionArity: Int =
      functionArityFor(tpnme.Function) max functionArityFor(tpnme.ImplicitFunction)

    /** Is a function name
     *    - FunctionN for N >= 0
     *    - ImplicitFunctionN for N >= 0
     *    - false otherwise
     */
    def isFunction: Boolean = functionArity >= 0

    /** Is a implicit function name
     *    - ImplicitFunctionN for N >= 0
     *    - false otherwise
     */
    def isImplicitFunction: Boolean = functionArityFor(tpnme.ImplicitFunction) >= 0

    /** Is a synthetic function name
     *    - FunctionN for N > 22
     *    - ImplicitFunctionN for N >= 0
     *    - false otherwise
     */
    def isSyntheticFunction: Boolean = {
      functionArityFor(tpnme.Function) > MaxImplementedFunctionArity ||
        functionArityFor(tpnme.ImplicitFunction) >= 0
    }

    /** Parsed function arity for function with some specific prefix */
    private def functionArityFor(prefix: Name): Int = {
      if (name.startsWith(prefix))
        try name.toString.substring(prefix.length).toInt
        catch { case _: NumberFormatException => -1 }
      else -1
    }

    /** The number of hops specified in an outer-select name */
    def outerSelectHops: Int = {
      require(isOuterSelect)
      name.dropRight(nme.OUTER_SELECT.length).toString.toInt
    }

    /** The name of the generic runtime operation corresponding to an array operation */
    def genericArrayOp: TermName = name match {
      case nme.apply => nme.array_apply
      case nme.length => nme.array_length
      case nme.update => nme.array_update
      case nme.clone_ => nme.array_clone
    }

    /** The name of the primitive runtime operation corresponding to an array operation */
    def primitiveArrayOp: TermName = name match {
      case nme.apply => nme.primitive.arrayApply
      case nme.length => nme.primitive.arrayLength
      case nme.update => nme.primitive.arrayUpdate
      case nme.clone_ => nme.clone_
    }

    def specializedFor(classTargs: List[Types.Type], classTargsNames: List[Name], methodTargs: List[Types.Type], methodTarsNames: List[Name])(implicit ctx: Context): name.ThisName = {

      def typeToTag(tp: Types.Type): Name = {
        tp.classSymbol match {
          case t if t eq defn.IntClass     => nme.specializedTypeNames.Int
          case t if t eq defn.BooleanClass => nme.specializedTypeNames.Boolean
          case t if t eq defn.ByteClass    => nme.specializedTypeNames.Byte
          case t if t eq defn.LongClass    => nme.specializedTypeNames.Long
          case t if t eq defn.ShortClass   => nme.specializedTypeNames.Short
          case t if t eq defn.FloatClass   => nme.specializedTypeNames.Float
          case t if t eq defn.UnitClass    => nme.specializedTypeNames.Void
          case t if t eq defn.DoubleClass  => nme.specializedTypeNames.Double
          case t if t eq defn.CharClass    => nme.specializedTypeNames.Char
          case _                           => nme.specializedTypeNames.Object
        }
      }

      val methodTags: Seq[Name] = (methodTargs zip methodTarsNames).sortBy(_._2).map(x => typeToTag(x._1))
      val classTags: Seq[Name] = (classTargs zip classTargsNames).sortBy(_._2).map(x => typeToTag(x._1))

      name.likeKinded(name ++ nme.specializedTypeNames.prefix ++
        methodTags.fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.separator ++
        classTags.fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.suffix)
    }

    /** If name length exceeds allowable limit, replace part of it by hash */
    def compactified(implicit ctx: Context): TermName = termName(compactify(name.toString))
  }

  // needed???
  private val Boxed = Map[TypeName, TypeName](
    tpnme.Boolean -> jtpnme.BoxedBoolean,
    tpnme.Byte -> jtpnme.BoxedByte,
    tpnme.Char -> jtpnme.BoxedCharacter,
    tpnme.Short -> jtpnme.BoxedShort,
    tpnme.Int -> jtpnme.BoxedInteger,
    tpnme.Long -> jtpnme.BoxedLong,
    tpnme.Float -> jtpnme.BoxedFloat,
    tpnme.Double -> jtpnme.BoxedDouble)

  implicit class TermNameDecorator(val name: TermName) extends AnyVal {
    import nme._

    def setterName: TermName =
      if (name.isFieldName) name.fieldToGetter.setterName
      else name ++ SETTER_SUFFIX

    def getterName: TermName =
      if (name.isFieldName) fieldToGetter
      else setterToGetter

    def fieldName: TermName =
      if (name.isSetterName) {
        if (name.is(TraitSetterName)) {
           val TraitSetterName(_, original) = name
          original.fieldName
        }
        else getterName.fieldName
      }
      else name.mapLast(n => (n ++ LOCAL_SUFFIX).asSimpleName)

    private def setterToGetter: TermName = {
      assert(name.endsWith(SETTER_SUFFIX), name + " is referenced as a setter but has wrong name format")
      name.mapLast(n => n.take(n.length - SETTER_SUFFIX.length).asSimpleName)
    }

    def fieldToGetter: TermName = {
      assert(name.isFieldName)
      name.mapLast(n => n.take(n.length - LOCAL_SUFFIX.length).asSimpleName)
    }

    /** Nominally, name$default$N, encoded for <init>
     *  @param  Post the parameters position.
     *  @note Default getter name suffixes start at 1, so `pos` has to be adjusted by +1
     */
    def defaultGetterName(pos: Int): TermName =
      DefaultGetterName(name, pos)

    /** Nominally, name from name$default$N, CONSTRUCTOR for <init> */
    def defaultGetterToMethod: TermName =
      name rewrite {
        case DefaultGetterName(methName, _) => methName
      }

    def defaultGetterToMethodOfMangled: TermName = {
        val p = name.indexOfSlice(DEFAULT_GETTER)
        if (p >= 0) {
          val q = name.take(p).asTermName
          // i.e., if (q.decoded == CONSTRUCTOR.toString) CONSTRUCTOR else q
          if (q == DEFAULT_GETTER_INIT) CONSTRUCTOR else q
        } else name
    }

    /** If this is a default getter, its index (starting from 0), else -1 */
    def defaultGetterIndex: Int =
      name collect {
        case DefaultGetterName(_, num) => num
      } getOrElse -1

    def defaultGetterIndexOfMangled: Int = {
      var i = name.length
      while (i > 0 && name(i - 1).isDigit) i -= 1
      if (i > 0 && i < name.length && name.take(i).endsWith(DEFAULT_GETTER))
        name.drop(i).toString.toInt - 1
      else
        -1
    }

    def stripScala2LocalSuffix: TermName =
      if (name.isScala2LocalSuffix) name.init.asTermName else name

    /** The name of an accessor for protected symbols. */
    def protectedAccessorName: TermName =
      PROTECTED_PREFIX ++ name.unexpandedName

    /** The name of a setter for protected symbols. Used for inherited Java fields. */
    def protectedSetterName: TermName =
      PROTECTED_SET_PREFIX ++ name.unexpandedName

    def moduleVarName: TermName =
      name ++ MODULE_VAR_SUFFIX

    /** The name unary_x for a prefix operator x */
    def toUnaryName: TermName = name match {
      case raw.MINUS => UNARY_-
      case raw.PLUS  => UNARY_+
      case raw.TILDE => UNARY_~
      case raw.BANG  => UNARY_!
      case _ => name
    }

    /** The name of a method which stands in for a primitive operation
     *  during structural type dispatch.
     */
    def primitiveInfixMethodName: TermName = name match {
      case OR   => takeOr
      case XOR  => takeXor
      case AND  => takeAnd
      case EQ   => testEqual
      case NE   => testNotEqual
      case ADD  => add
      case SUB  => subtract
      case MUL  => multiply
      case DIV  => divide
      case MOD  => takeModulo
      case LSL  => shiftSignedLeft
      case LSR  => shiftLogicalRight
      case ASR  => shiftSignedRight
      case LT   => testLessThan
      case LE   => testLessOrEqualThan
      case GE   => testGreaterOrEqualThan
      case GT   => testGreaterThan
      case ZOR  => takeConditionalOr
      case ZAND => takeConditionalAnd
      case _    => NO_NAME
    }

    /** Postfix/prefix, really.
     */
    def primitivePostfixMethodName: TermName = name match {
      case UNARY_!    => takeNot
      case UNARY_+    => positive
      case UNARY_-    => negate
      case UNARY_~    => complement
      case `toByte`   => toByte
      case `toShort`  => toShort
      case `toChar`   => toCharacter
      case `toInt`    => toInteger
      case `toLong`   => toLong
      case `toFloat`  => toFloat
      case `toDouble` => toDouble
      case _          => NO_NAME
    }

    def primitiveMethodName: TermName =
      primitiveInfixMethodName match {
        case NO_NAME => primitivePostfixMethodName
        case name => name
      }

    def lazyLocalName = name ++ nme.LAZY_LOCAL
    def nonLazyName = {
      assert(name.isLazyLocal)
      name.dropRight(nme.LAZY_LOCAL.length)
    }

    def inlineAccessorName = nme.INLINE_ACCESSOR_PREFIX ++ name ++ "$"

    def unmangleMethodName: TermName =
      if (name.isSimple) {
        val idx = name.defaultGetterIndexOfMangled
        if (idx >= 0) name.defaultGetterToMethodOfMangled.defaultGetterName(idx)
        else name
      }
      else name

    def unmangleSuperName: TermName =
      if (name.isSimple && name.startsWith(str.SUPER_PREFIX))
        SuperAccessorName(name.drop(str.SUPER_PREFIX.length).asTermName)
      else name
  }

  private final val FalseSuper = "$$super".toTermName
  private val FalseSuperLength = FalseSuper.length
}