| OLD | NEW |
| 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 part of js_backend; | 5 part of js_backend; |
| 6 | 6 |
| 7 /** | 7 /** |
| 8 * Assigns JavaScript identifiers to Dart variables, class-names and members. | 8 * Assigns JavaScript identifiers to Dart variables, class-names and members. |
| 9 */ | 9 */ |
| 10 class MinifyNamer extends Namer { | 10 class MinifyNamer extends Namer with _MinifiedFieldNamer { |
| 11 MinifyNamer(Compiler compiler) : super(compiler) { | 11 MinifyNamer(Compiler compiler) : super(compiler) { |
| 12 reserveBackendNames(); | 12 reserveBackendNames(); |
| 13 fieldRegistry = new _FieldNamingRegistry(this); | 13 fieldRegistry = new _FieldNamingRegistry(this); |
| 14 } | 14 } |
| 15 | 15 |
| 16 _FieldNamingRegistry fieldRegistry; |
| 17 |
| 16 String get isolateName => 'I'; | 18 String get isolateName => 'I'; |
| 17 String get isolatePropertiesName => 'p'; | 19 String get isolatePropertiesName => 'p'; |
| 18 bool get shouldMinify => true; | 20 bool get shouldMinify => true; |
| 19 | 21 |
| 20 final String getterPrefix = 'g'; | 22 final String getterPrefix = 'g'; |
| 21 final String setterPrefix = 's'; | 23 final String setterPrefix = 's'; |
| 22 final String callPrefix = ''; // this will create function names $<n> | 24 final String callPrefix = ''; // this will create function names $<n> |
| 23 | 25 |
| 24 final ALPHABET_CHARACTERS = 52; // a-zA-Z. | 26 final ALPHABET_CHARACTERS = 52; // a-zA-Z. |
| 25 final ALPHANUMERIC_CHARACTERS = 62; // a-zA-Z0-9. | 27 final ALPHANUMERIC_CHARACTERS = 62; // a-zA-Z0-9. |
| 26 | 28 |
| 27 _FieldNamingRegistry fieldRegistry; | |
| 28 | |
| 29 /// You can pass an invalid identifier to this and unlike its non-minifying | 29 /// You can pass an invalid identifier to this and unlike its non-minifying |
| 30 /// counterpart it will never return the proposedName as the new fresh name. | 30 /// counterpart it will never return the proposedName as the new fresh name. |
| 31 /// | 31 /// |
| 32 /// [sanitizeForNatives] and [sanitizeForAnnotations] are ignored because the | 32 /// [sanitizeForNatives] and [sanitizeForAnnotations] are ignored because the |
| 33 /// minified names will always avoid clashing with annotated names or natives. | 33 /// minified names will always avoid clashing with annotated names or natives. |
| 34 @override |
| 34 jsAst.Name getFreshName(String proposedName, | 35 jsAst.Name getFreshName(String proposedName, |
| 35 Set<String> usedNames, | 36 Set<String> usedNames, |
| 36 Map<String, String> suggestedNames, | 37 Map<String, String> suggestedNames, |
| 37 {bool sanitizeForNatives: false, | 38 {bool sanitizeForNatives: false, |
| 38 bool sanitizeForAnnotations: false}) { | 39 bool sanitizeForAnnotations: false}) { |
| 39 String freshName; | 40 String freshName; |
| 40 String suggestion = suggestedNames[proposedName]; | 41 String suggestion = suggestedNames[proposedName]; |
| 41 if (suggestion != null && !usedNames.contains(suggestion)) { | 42 if (suggestion != null && !usedNames.contains(suggestion)) { |
| 42 freshName = suggestion; | 43 freshName = suggestion; |
| 43 } else { | 44 } else { |
| 44 freshName = _getUnusedName(proposedName, usedNames, | 45 freshName = _getUnusedName(proposedName, usedNames, |
| 45 suggestedNames.values); | 46 suggestedNames.values); |
| 46 } | 47 } |
| 47 usedNames.add(freshName); | 48 usedNames.add(freshName); |
| 48 return new StringBackedName(freshName); | 49 return new StringBackedName(freshName); |
| (...skipping 188 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 237 return $A + x - 26; | 238 return $A + x - 26; |
| 238 } | 239 } |
| 239 | 240 |
| 240 int _alphaNumericNumber(int x) { | 241 int _alphaNumericNumber(int x) { |
| 241 if (x >= ALPHANUMERIC_CHARACTERS) x %= ALPHANUMERIC_CHARACTERS; | 242 if (x >= ALPHANUMERIC_CHARACTERS) x %= ALPHANUMERIC_CHARACTERS; |
| 242 if (x < 26) return $a + x; | 243 if (x < 26) return $a + x; |
| 243 if (x < 52) return $A + x - 26; | 244 if (x < 52) return $A + x - 26; |
| 244 return $0 + x - 52; | 245 return $0 + x - 52; |
| 245 } | 246 } |
| 246 | 247 |
| 248 @override |
| 247 jsAst.Name instanceFieldPropertyName(Element element) { | 249 jsAst.Name instanceFieldPropertyName(Element element) { |
| 248 if (element.hasFixedBackendName) { | 250 jsAst.Name proposed = _minifiedInstanceFieldPropertyName(element); |
| 249 return new StringBackedName(element.fixedBackendName); | 251 if (proposed != null) { |
| 252 return proposed; |
| 250 } | 253 } |
| 251 | 254 return super.instanceFieldPropertyName(element); |
| 252 _FieldNamingScope names; | |
| 253 if (element is BoxFieldElement) { | |
| 254 names = new _FieldNamingScope.forBox(element.box, fieldRegistry); | |
| 255 } else { | |
| 256 ClassElement cls = element is ClosureFieldElement | |
| 257 ? element.closureClass : element.enclosingClass; | |
| 258 names = new _FieldNamingScope.forClass(cls, compiler.world, | |
| 259 fieldRegistry); | |
| 260 } | |
| 261 | |
| 262 // The inheritance scope based naming did not yield a name. For instance, | |
| 263 // this could be because the field belongs to a mixin. | |
| 264 if (!names.containsField(element)) { | |
| 265 return super.instanceFieldPropertyName(element); | |
| 266 } | |
| 267 | |
| 268 return names[element]; | |
| 269 } | 255 } |
| 270 } | 256 } |
| 271 | 257 |
| 272 /** | |
| 273 * Encapsulates the global state of field naming. | |
| 274 */ | |
| 275 class _FieldNamingRegistry { | |
| 276 final MinifyNamer namer; | |
| 277 | |
| 278 final Map<Entity, _FieldNamingScope> scopes = | |
| 279 new Map<Entity, _FieldNamingScope>(); | |
| 280 | |
| 281 final Map<Entity, jsAst.Name> globalNames = new Map<Entity, jsAst.Name>(); | |
| 282 | |
| 283 int globalCount = 0; | |
| 284 | |
| 285 final List<jsAst.Name> nameStore = new List<jsAst.Name>(); | |
| 286 | |
| 287 _FieldNamingRegistry(this.namer); | |
| 288 | |
| 289 jsAst.Name getName(int count) { | |
| 290 if (count >= nameStore.length) { | |
| 291 // The namer usually does not use certain names as they clash with | |
| 292 // existing properties on JS objects (see [_reservedNativeProperties]). | |
| 293 // However, some of them are really short and safe to use for fields. | |
| 294 // Thus, we shortcut the namer to use those first. | |
| 295 if (count < MinifyNamer._reservedNativeProperties.length && | |
| 296 MinifyNamer._reservedNativeProperties[count].length <= 2) { | |
| 297 nameStore.add(new StringBackedName( | |
| 298 MinifyNamer._reservedNativeProperties[count])); | |
| 299 } else { | |
| 300 nameStore.add(namer.getFreshName("field$count", | |
| 301 namer.usedInstanceNames, namer.suggestedInstanceNames)); | |
| 302 } | |
| 303 } | |
| 304 | |
| 305 return nameStore[count]; | |
| 306 } | |
| 307 } | |
| 308 | |
| 309 /** | |
| 310 * A [_FieldNamingScope] encodes a node in the inheritance tree of the current | |
| 311 * class hierarchy. The root node typically is the node corresponding to the | |
| 312 * `Object` class. It is used to assign a unique name to each field of a class. | |
| 313 * Unique here means unique wrt. all fields along the path back to the root. | |
| 314 * This is achieved at construction time via the [_fieldNameCounter] field that | |
| 315 * counts the number of fields on the path to the root node that have been | |
| 316 * encountered so far. | |
| 317 * | |
| 318 * Obviously, this only works if no fields are added to a parent node after its | |
| 319 * children have added their first field. | |
| 320 */ | |
| 321 class _FieldNamingScope { | |
| 322 final _FieldNamingScope superScope; | |
| 323 final Entity container; | |
| 324 final Map<Element, jsAst.Name> names = new Maplet<Element, jsAst.Name>(); | |
| 325 final _FieldNamingRegistry registry; | |
| 326 | |
| 327 /// Naming counter used for fields of ordinary classes. | |
| 328 int _fieldNameCounter; | |
| 329 | |
| 330 /// The number of fields along the superclass chain that use inheritance | |
| 331 /// based naming, including the ones allocated for this scope. | |
| 332 int get inheritanceBasedFieldNameCounter => _fieldNameCounter; | |
| 333 | |
| 334 /// The number of locally used fields. Depending on the naming source | |
| 335 /// (e.g. inheritance based or globally unique for mixixns) this | |
| 336 /// might be different from [inheritanceBasedFieldNameCounter]. | |
| 337 int get _localFieldNameCounter => _fieldNameCounter; | |
| 338 void set _localFieldNameCounter(int val) { _fieldNameCounter = val; } | |
| 339 | |
| 340 factory _FieldNamingScope.forClass(ClassElement cls, ClassWorld world, | |
| 341 _FieldNamingRegistry registry) { | |
| 342 _FieldNamingScope result = registry.scopes[cls]; | |
| 343 if (result != null) return result; | |
| 344 | |
| 345 if (world.isUsedAsMixin(cls)) { | |
| 346 result = new _MixinFieldNamingScope.mixin(cls, registry); | |
| 347 } else { | |
| 348 if (cls.superclass == null) { | |
| 349 result = new _FieldNamingScope.rootScope(cls, registry); | |
| 350 } else { | |
| 351 _FieldNamingScope superScope = new _FieldNamingScope.forClass( | |
| 352 cls.superclass, world, registry); | |
| 353 if (cls.isMixinApplication) { | |
| 354 result = new _MixinFieldNamingScope.mixedIn(cls, superScope, | |
| 355 registry); | |
| 356 } else { | |
| 357 result = new _FieldNamingScope.inherit(cls, superScope, registry); | |
| 358 } | |
| 359 } | |
| 360 } | |
| 361 | |
| 362 cls.forEachInstanceField((cls, field) => result.add(field)); | |
| 363 | |
| 364 registry.scopes[cls] = result; | |
| 365 return result; | |
| 366 } | |
| 367 | |
| 368 factory _FieldNamingScope.forBox(Local box, _FieldNamingRegistry registry) { | |
| 369 return registry.scopes.putIfAbsent(box, | |
| 370 () => new _BoxFieldNamingScope(box, registry)); | |
| 371 } | |
| 372 | |
| 373 _FieldNamingScope.rootScope(this.container, this.registry) | |
| 374 : superScope = null, | |
| 375 _fieldNameCounter = 0; | |
| 376 | |
| 377 _FieldNamingScope.inherit(this.container, this.superScope, this.registry) { | |
| 378 _fieldNameCounter = superScope.inheritanceBasedFieldNameCounter; | |
| 379 } | |
| 380 | |
| 381 /** | |
| 382 * Checks whether [name] is already used in the current scope chain. | |
| 383 */ | |
| 384 _isNameUnused(jsAst.Name name) { | |
| 385 return !names.values.contains(name) && | |
| 386 ((superScope == null) || superScope._isNameUnused(name)); | |
| 387 } | |
| 388 | |
| 389 jsAst.Name _nextName() => registry.getName(_localFieldNameCounter++); | |
| 390 | |
| 391 jsAst.Name operator[](Element field) { | |
| 392 jsAst.Name name = names[field]; | |
| 393 if (name == null && superScope != null) return superScope[field]; | |
| 394 return name; | |
| 395 } | |
| 396 | |
| 397 void add(Element field) { | |
| 398 if (names.containsKey(field)) return; | |
| 399 | |
| 400 jsAst.Name value = _nextName(); | |
| 401 assert(invariant(field, _isNameUnused(value))); | |
| 402 names[field] = value; | |
| 403 } | |
| 404 | |
| 405 bool containsField(Element field) => names.containsKey(field); | |
| 406 } | |
| 407 | |
| 408 /** | |
| 409 * Field names for mixins have two constraints: They need to be unique in the | |
| 410 * hierarchy of each application of a mixin and they need to be the same for | |
| 411 * all applications of a mixin. To achieve this, we use global naming for | |
| 412 * mixins from the same name pool as fields and add a `$` at the end to ensure | |
| 413 * they do not collide with normal field names. The `$` sign is typically used | |
| 414 * as a separator between method names and argument counts and does not appear | |
| 415 * in generated names themselves. | |
| 416 */ | |
| 417 class _MixinFieldNamingScope extends _FieldNamingScope { | |
| 418 int get _localFieldNameCounter => registry.globalCount; | |
| 419 void set _localFieldNameCounter(int val) { registry.globalCount = val; } | |
| 420 | |
| 421 Map<Entity, jsAst.Name> get names => registry.globalNames; | |
| 422 | |
| 423 _MixinFieldNamingScope.mixin(ClassElement cls, _FieldNamingRegistry registry) | |
| 424 : super.rootScope(cls, registry); | |
| 425 | |
| 426 _MixinFieldNamingScope.mixedIn(MixinApplicationElement container, | |
| 427 _FieldNamingScope superScope, _FieldNamingRegistry registry) | |
| 428 : super.inherit(container, superScope, registry); | |
| 429 | |
| 430 jsAst.Name _nextName() { | |
| 431 jsAst.Name proposed = super._nextName(); | |
| 432 return new CompoundName([proposed, Namer._literalDollar]); | |
| 433 } | |
| 434 } | |
| 435 | |
| 436 /** | |
| 437 * [BoxFieldElement] fields work differently in that they do not belong to an | |
| 438 * actual class but an anonymous box associated to a [Local]. As there is no | |
| 439 * inheritance chain, we do not need to compute fields a priori but can assign | |
| 440 * names on the fly. | |
| 441 */ | |
| 442 class _BoxFieldNamingScope extends _FieldNamingScope { | |
| 443 _BoxFieldNamingScope(Local box, _FieldNamingRegistry registry) : | |
| 444 super.rootScope(box, registry); | |
| 445 | |
| 446 bool containsField(_) => true; | |
| 447 | |
| 448 jsAst.Name operator[](Element field) { | |
| 449 if (!names.containsKey(field)) add(field); | |
| 450 return names[field]; | |
| 451 } | |
| 452 } | |
| OLD | NEW |