| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 part of js_backend; | |
| 6 | |
| 7 /** | |
| 8 * Assigns JavaScript identifiers to Dart variables, class-names and members. | |
| 9 */ | |
| 10 class Namer implements ClosureNamer { | |
| 11 | |
| 12 static const javaScriptKeywords = const <String>[ | |
| 13 // These are current keywords. | |
| 14 "break", "delete", "function", "return", "typeof", "case", "do", "if", | |
| 15 "switch", "var", "catch", "else", "in", "this", "void", "continue", | |
| 16 "false", "instanceof", "throw", "while", "debugger", "finally", "new", | |
| 17 "true", "with", "default", "for", "null", "try", | |
| 18 | |
| 19 // These are future keywords. | |
| 20 "abstract", "double", "goto", "native", "static", "boolean", "enum", | |
| 21 "implements", "package", "super", "byte", "export", "import", "private", | |
| 22 "synchronized", "char", "extends", "int", "protected", "throws", | |
| 23 "class", "final", "interface", "public", "transient", "const", "float", | |
| 24 "long", "short", "volatile" | |
| 25 ]; | |
| 26 | |
| 27 static const reservedPropertySymbols = | |
| 28 const <String>["__proto__", "prototype", "constructor", "call"]; | |
| 29 | |
| 30 // Symbols that we might be using in our JS snippets. | |
| 31 static const reservedGlobalSymbols = const <String>[ | |
| 32 // Section references are from Ecma-262 | |
| 33 // (http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pd
f) | |
| 34 | |
| 35 // 15.1.1 Value Properties of the Global Object | |
| 36 "NaN", "Infinity", "undefined", | |
| 37 | |
| 38 // 15.1.2 Function Properties of the Global Object | |
| 39 "eval", "parseInt", "parseFloat", "isNaN", "isFinite", | |
| 40 | |
| 41 // 15.1.3 URI Handling Function Properties | |
| 42 "decodeURI", "decodeURIComponent", | |
| 43 "encodeURI", | |
| 44 "encodeURIComponent", | |
| 45 | |
| 46 // 15.1.4 Constructor Properties of the Global Object | |
| 47 "Object", "Function", "Array", "String", "Boolean", "Number", "Date", | |
| 48 "RegExp", "Error", "EvalError", "RangeError", "ReferenceError", | |
| 49 "SyntaxError", "TypeError", "URIError", | |
| 50 | |
| 51 // 15.1.5 Other Properties of the Global Object | |
| 52 "Math", | |
| 53 | |
| 54 // 10.1.6 Activation Object | |
| 55 "arguments", | |
| 56 | |
| 57 // B.2 Additional Properties (non-normative) | |
| 58 "escape", "unescape", | |
| 59 | |
| 60 // Window props (https://developer.mozilla.org/en/DOM/window) | |
| 61 "applicationCache", "closed", "Components", "content", "controllers", | |
| 62 "crypto", "defaultStatus", "dialogArguments", "directories", | |
| 63 "document", "frameElement", "frames", "fullScreen", "globalStorage", | |
| 64 "history", "innerHeight", "innerWidth", "length", | |
| 65 "location", "locationbar", "localStorage", "menubar", | |
| 66 "mozInnerScreenX", "mozInnerScreenY", "mozScreenPixelsPerCssPixel", | |
| 67 "name", "navigator", "opener", "outerHeight", "outerWidth", | |
| 68 "pageXOffset", "pageYOffset", "parent", "personalbar", "pkcs11", | |
| 69 "returnValue", "screen", "scrollbars", "scrollMaxX", "scrollMaxY", | |
| 70 "self", "sessionStorage", "sidebar", "status", "statusbar", "toolbar", | |
| 71 "top", "window", | |
| 72 | |
| 73 // Window methods (https://developer.mozilla.org/en/DOM/window) | |
| 74 "alert", "addEventListener", "atob", "back", "blur", "btoa", | |
| 75 "captureEvents", "clearInterval", "clearTimeout", "close", "confirm", | |
| 76 "disableExternalCapture", "dispatchEvent", "dump", | |
| 77 "enableExternalCapture", "escape", "find", "focus", "forward", | |
| 78 "GeckoActiveXObject", "getAttention", "getAttentionWithCycleCount", | |
| 79 "getComputedStyle", "getSelection", "home", "maximize", "minimize", | |
| 80 "moveBy", "moveTo", "open", "openDialog", "postMessage", "print", | |
| 81 "prompt", "QueryInterface", "releaseEvents", "removeEventListener", | |
| 82 "resizeBy", "resizeTo", "restore", "routeEvent", "scroll", "scrollBy", | |
| 83 "scrollByLines", "scrollByPages", "scrollTo", "setInterval", | |
| 84 "setResizeable", "setTimeout", "showModalDialog", "sizeToContent", | |
| 85 "stop", "uuescape", "updateCommands", "XPCNativeWrapper", | |
| 86 "XPCSafeJSOjbectWrapper", | |
| 87 | |
| 88 // Mozilla Window event handlers, same cite | |
| 89 "onabort", "onbeforeunload", "onchange", "onclick", "onclose", | |
| 90 "oncontextmenu", "ondragdrop", "onerror", "onfocus", "onhashchange", | |
| 91 "onkeydown", "onkeypress", "onkeyup", "onload", "onmousedown", | |
| 92 "onmousemove", "onmouseout", "onmouseover", "onmouseup", | |
| 93 "onmozorientation", "onpaint", "onreset", "onresize", "onscroll", | |
| 94 "onselect", "onsubmit", "onunload", | |
| 95 | |
| 96 // Safari Web Content Guide | |
| 97 // http://developer.apple.com/library/safari/#documentation/AppleApplication
s/Reference/SafariWebContent/SafariWebContent.pdf | |
| 98 // WebKit Window member data, from WebKit DOM Reference | |
| 99 // (http://developer.apple.com/safari/library/documentation/AppleApplication
s/Reference/WebKitDOMRef/DOMWindow_idl/Classes/DOMWindow/index.html) | |
| 100 "ontouchcancel", "ontouchend", "ontouchmove", "ontouchstart", | |
| 101 "ongesturestart", "ongesturechange", "ongestureend", | |
| 102 | |
| 103 // extra window methods | |
| 104 "uneval", | |
| 105 | |
| 106 // keywords https://developer.mozilla.org/en/New_in_JavaScript_1.7, | |
| 107 // https://developer.mozilla.org/en/New_in_JavaScript_1.8.1 | |
| 108 "getPrototypeOf", "let", "yield", | |
| 109 | |
| 110 // "future reserved words" | |
| 111 "abstract", "int", "short", "boolean", "interface", "static", "byte", | |
| 112 "long", "char", "final", "native", "synchronized", "float", "package", | |
| 113 "throws", "goto", "private", "transient", "implements", "protected", | |
| 114 "volatile", "double", "public", | |
| 115 | |
| 116 // IE methods | |
| 117 // (http://msdn.microsoft.com/en-us/library/ms535873(VS.85).aspx#) | |
| 118 "attachEvent", "clientInformation", "clipboardData", "createPopup", | |
| 119 "dialogHeight", "dialogLeft", "dialogTop", "dialogWidth", | |
| 120 "onafterprint", "onbeforedeactivate", "onbeforeprint", | |
| 121 "oncontrolselect", "ondeactivate", "onhelp", "onresizeend", | |
| 122 | |
| 123 // Common browser-defined identifiers not defined in ECMAScript | |
| 124 "event", "external", "Debug", "Enumerator", "Global", "Image", | |
| 125 "ActiveXObject", "VBArray", "Components", | |
| 126 | |
| 127 // Functions commonly defined on Object | |
| 128 "toString", "getClass", "constructor", "prototype", "valueOf", | |
| 129 | |
| 130 // Client-side JavaScript identifiers | |
| 131 "Anchor", "Applet", "Attr", "Canvas", "CanvasGradient", | |
| 132 "CanvasPattern", "CanvasRenderingContext2D", "CDATASection", | |
| 133 "CharacterData", "Comment", "CSS2Properties", "CSSRule", | |
| 134 "CSSStyleSheet", "Document", "DocumentFragment", "DocumentType", | |
| 135 "DOMException", "DOMImplementation", "DOMParser", "Element", "Event", | |
| 136 "ExternalInterface", "FlashPlayer", "Form", "Frame", "History", | |
| 137 "HTMLCollection", "HTMLDocument", "HTMLElement", "IFrame", "Image", | |
| 138 "Input", "JSObject", "KeyEvent", "Link", "Location", "MimeType", | |
| 139 "MouseEvent", "Navigator", "Node", "NodeList", "Option", "Plugin", | |
| 140 "ProcessingInstruction", "Range", "RangeException", "Screen", "Select", | |
| 141 "Table", "TableCell", "TableRow", "TableSelection", "Text", "TextArea", | |
| 142 "UIEvent", "Window", "XMLHttpRequest", "XMLSerializer", | |
| 143 "XPathException", "XPathResult", "XSLTProcessor", | |
| 144 | |
| 145 // These keywords trigger the loading of the java-plugin. For the | |
| 146 // next-generation plugin, this results in starting a new Java process. | |
| 147 "java", "Packages", "netscape", "sun", "JavaObject", "JavaClass", | |
| 148 "JavaArray", "JavaMember", | |
| 149 ]; | |
| 150 | |
| 151 static const reservedGlobalObjectNames = const <String>[ | |
| 152 "A", | |
| 153 "B", | |
| 154 "C", // Global object for *C*onstants. | |
| 155 "D", | |
| 156 "E", | |
| 157 "F", | |
| 158 "G", | |
| 159 "H", // Global object for internal (*H*elper) libraries. | |
| 160 // I is used for used for the Isolate function. | |
| 161 "J", // Global object for the interceptor library. | |
| 162 "K", | |
| 163 "L", | |
| 164 "M", | |
| 165 "N", | |
| 166 "O", | |
| 167 "P", // Global object for other *P*latform libraries. | |
| 168 "Q", | |
| 169 "R", | |
| 170 "S", | |
| 171 "T", | |
| 172 "U", | |
| 173 "V", | |
| 174 "W", // Global object for *W*eb libraries (dart:html). | |
| 175 "X", | |
| 176 "Y", | |
| 177 "Z", | |
| 178 ]; | |
| 179 | |
| 180 static const reservedGlobalHelperFunctions = const <String>[ | |
| 181 "init", | |
| 182 "Isolate", | |
| 183 ]; | |
| 184 | |
| 185 static final userGlobalObjects = new List.from(reservedGlobalObjectNames) | |
| 186 ..remove('C') | |
| 187 ..remove('H') | |
| 188 ..remove('J') | |
| 189 ..remove('P') | |
| 190 ..remove('W'); | |
| 191 | |
| 192 Set<String> _jsReserved = null; | |
| 193 /// Names that cannot be used by members, top level and static | |
| 194 /// methods. | |
| 195 Set<String> get jsReserved { | |
| 196 if (_jsReserved == null) { | |
| 197 _jsReserved = new Set<String>(); | |
| 198 _jsReserved.addAll(javaScriptKeywords); | |
| 199 _jsReserved.addAll(reservedPropertySymbols); | |
| 200 } | |
| 201 return _jsReserved; | |
| 202 } | |
| 203 | |
| 204 Set<String> _jsVariableReserved = null; | |
| 205 /// Names that cannot be used by local variables and parameters. | |
| 206 Set<String> get jsVariableReserved { | |
| 207 if (_jsVariableReserved == null) { | |
| 208 _jsVariableReserved = new Set<String>(); | |
| 209 _jsVariableReserved.addAll(javaScriptKeywords); | |
| 210 _jsVariableReserved.addAll(reservedPropertySymbols); | |
| 211 _jsVariableReserved.addAll(reservedGlobalSymbols); | |
| 212 _jsVariableReserved.addAll(reservedGlobalObjectNames); | |
| 213 // 26 letters in the alphabet, 25 not counting I. | |
| 214 assert(reservedGlobalObjectNames.length == 25); | |
| 215 _jsVariableReserved.addAll(reservedGlobalHelperFunctions); | |
| 216 } | |
| 217 return _jsVariableReserved; | |
| 218 } | |
| 219 | |
| 220 final String currentIsolate = r'$'; | |
| 221 final String getterPrefix = r'get$'; | |
| 222 final String setterPrefix = r'set$'; | |
| 223 final String metadataField = '@'; | |
| 224 final String callPrefix = 'call'; | |
| 225 final String callCatchAllName = r'call$catchAll'; | |
| 226 final String reflectableField = r'$reflectable'; | |
| 227 final String defaultValuesField = r'$defaultValues'; | |
| 228 final String methodsWithOptionalArgumentsField = | |
| 229 r'$methodsWithOptionalArguments'; | |
| 230 | |
| 231 final String classDescriptorProperty = r'^'; | |
| 232 | |
| 233 // Name of property in a class description for the native dispatch metadata. | |
| 234 final String nativeSpecProperty = '%'; | |
| 235 | |
| 236 static final RegExp IDENTIFIER = new RegExp(r'^[A-Za-z_$][A-Za-z0-9_$]*$'); | |
| 237 static final RegExp NON_IDENTIFIER_CHAR = new RegExp(r'[^A-Za-z_0-9$]'); | |
| 238 | |
| 239 /** | |
| 240 * Map from top-level or static elements to their unique identifiers provided | |
| 241 * by [getName]. | |
| 242 * | |
| 243 * Invariant: Keys must be declaration elements. | |
| 244 */ | |
| 245 final Compiler compiler; | |
| 246 final Map<Element, String> globals; | |
| 247 final Map<String, LibraryElement> shortPrivateNameOwners; | |
| 248 | |
| 249 final Set<String> usedGlobalNames; | |
| 250 final Set<String> usedInstanceNames; | |
| 251 final Map<String, String> globalNameMap; | |
| 252 final Map<String, String> suggestedGlobalNames; | |
| 253 final Map<String, String> instanceNameMap; | |
| 254 final Map<String, String> suggestedInstanceNames; | |
| 255 | |
| 256 final Map<String, String> operatorNameMap; | |
| 257 final Map<String, int> popularNameCounters; | |
| 258 | |
| 259 final Map<ConstantValue, String> constantNames; | |
| 260 final Map<ConstantValue, String> constantLongNames; | |
| 261 ConstantCanonicalHasher constantHasher; | |
| 262 | |
| 263 // All alphanumeric characters. | |
| 264 static const String _alphaNumeric = | |
| 265 'abcdefghijklmnopqrstuvwxyzABZDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; | |
| 266 | |
| 267 Namer(Compiler compiler) | |
| 268 : compiler = compiler, | |
| 269 globals = new Map<Element, String>(), | |
| 270 shortPrivateNameOwners = new Map<String, LibraryElement>(), | |
| 271 usedGlobalNames = new Set<String>(), | |
| 272 usedInstanceNames = new Set<String>(), | |
| 273 instanceNameMap = new Map<String, String>(), | |
| 274 operatorNameMap = new Map<String, String>(), | |
| 275 globalNameMap = new Map<String, String>(), | |
| 276 suggestedGlobalNames = new Map<String, String>(), | |
| 277 suggestedInstanceNames = new Map<String, String>(), | |
| 278 popularNameCounters = new Map<String, int>(), | |
| 279 constantNames = new Map<ConstantValue, String>(), | |
| 280 constantLongNames = new Map<ConstantValue, String>(), | |
| 281 constantHasher = new ConstantCanonicalHasher(compiler), | |
| 282 functionTypeNamer = new FunctionTypeNamer(compiler); | |
| 283 | |
| 284 JavaScriptBackend get backend => compiler.backend; | |
| 285 | |
| 286 String get isolateName => 'Isolate'; | |
| 287 String get isolatePropertiesName => r'$isolateProperties'; | |
| 288 /** | |
| 289 * Some closures must contain their name. The name is stored in | |
| 290 * [STATIC_CLOSURE_NAME_NAME]. | |
| 291 */ | |
| 292 String get STATIC_CLOSURE_NAME_NAME => r'$name'; | |
| 293 String get closureInvocationSelectorName => Compiler.CALL_OPERATOR_NAME; | |
| 294 bool get shouldMinify => false; | |
| 295 | |
| 296 String getNameForJsGetName(Node node, String name) { | |
| 297 switch (name) { | |
| 298 case 'GETTER_PREFIX': return getterPrefix; | |
| 299 case 'SETTER_PREFIX': return setterPrefix; | |
| 300 case 'CALL_PREFIX': return callPrefix; | |
| 301 case 'CALL_CATCH_ALL': return callCatchAllName; | |
| 302 case 'REFLECTABLE': return reflectableField; | |
| 303 case 'CLASS_DESCRIPTOR_PROPERTY': return classDescriptorProperty; | |
| 304 default: | |
| 305 compiler.reportError( | |
| 306 node, MessageKind.GENERIC, | |
| 307 {'text': 'Error: Namer has no name for "$name".'}); | |
| 308 return 'BROKEN'; | |
| 309 } | |
| 310 } | |
| 311 | |
| 312 String constantName(ConstantValue constant) { | |
| 313 // In the current implementation it doesn't make sense to give names to | |
| 314 // function constants since the function-implementation itself serves as | |
| 315 // constant and can be accessed directly. | |
| 316 assert(!constant.isFunction); | |
| 317 String result = constantNames[constant]; | |
| 318 if (result == null) { | |
| 319 String longName = constantLongName(constant); | |
| 320 result = getFreshName(longName, usedGlobalNames, suggestedGlobalNames, | |
| 321 ensureSafe: true); | |
| 322 constantNames[constant] = result; | |
| 323 } | |
| 324 return result; | |
| 325 } | |
| 326 | |
| 327 // The long name is unminified and may have collisions. | |
| 328 String constantLongName(ConstantValue constant) { | |
| 329 String longName = constantLongNames[constant]; | |
| 330 if (longName == null) { | |
| 331 longName = new ConstantNamingVisitor(compiler, constantHasher) | |
| 332 .getName(constant); | |
| 333 constantLongNames[constant] = longName; | |
| 334 } | |
| 335 return longName; | |
| 336 } | |
| 337 | |
| 338 String breakLabelName(LabelDefinition label) { | |
| 339 return '\$${label.labelName}\$${label.target.nestingLevel}'; | |
| 340 } | |
| 341 | |
| 342 String implicitBreakLabelName(JumpTarget target) { | |
| 343 return '\$${target.nestingLevel}'; | |
| 344 } | |
| 345 | |
| 346 // We sometimes handle continue targets differently from break targets, | |
| 347 // so we have special continue-only labels. | |
| 348 String continueLabelName(LabelDefinition label) { | |
| 349 return 'c\$${label.labelName}\$${label.target.nestingLevel}'; | |
| 350 } | |
| 351 | |
| 352 String implicitContinueLabelName(JumpTarget target) { | |
| 353 return 'c\$${target.nestingLevel}'; | |
| 354 } | |
| 355 | |
| 356 /** | |
| 357 * If the [name] is not private returns [:name:]. Otherwise | |
| 358 * mangles the [name] so that each library has a unique name. | |
| 359 */ | |
| 360 String privateName(LibraryElement library, String name) { | |
| 361 // Public names are easy. | |
| 362 String nameString = name; | |
| 363 if (!isPrivateName(name)) return nameString; | |
| 364 | |
| 365 // The first library asking for a short private name wins. | |
| 366 LibraryElement owner = | |
| 367 shortPrivateNameOwners.putIfAbsent(nameString, () => library); | |
| 368 | |
| 369 if (owner == library && !nameString.contains('\$')) { | |
| 370 // Since the name doesn't contain $ it doesn't clash with any | |
| 371 // of the private names that have the library name as the prefix. | |
| 372 return nameString; | |
| 373 } else { | |
| 374 // Make sure to return a private name that starts with _ so it | |
| 375 // cannot clash with any public names. | |
| 376 String libraryName = getNameOfLibrary(library); | |
| 377 return '_$libraryName\$$nameString'; | |
| 378 } | |
| 379 } | |
| 380 | |
| 381 String instanceMethodName(FunctionElement element) { | |
| 382 // TODO(ahe): Could this be: return invocationName(new | |
| 383 // Selector.fromElement(element))? | |
| 384 String elementName = element.name; | |
| 385 String name = operatorNameToIdentifier(elementName); | |
| 386 if (name != elementName) return getMappedOperatorName(name); | |
| 387 | |
| 388 LibraryElement library = element.library; | |
| 389 if (element.isGenerativeConstructorBody) { | |
| 390 name = Elements.reconstructConstructorNameSourceString(element); | |
| 391 } | |
| 392 FunctionSignature signature = element.functionSignature; | |
| 393 // We don't mangle the closure invoking function name because it | |
| 394 // is generated by string concatenation in applyFunction from | |
| 395 // js_helper.dart. To keep code size down, we potentially shorten | |
| 396 // the prefix though. | |
| 397 String methodName; | |
| 398 if (name == closureInvocationSelectorName) { | |
| 399 methodName = '$callPrefix\$${signature.parameterCount}'; | |
| 400 } else { | |
| 401 methodName = '${privateName(library, name)}\$${signature.parameterCount}'; | |
| 402 } | |
| 403 if (signature.optionalParametersAreNamed && | |
| 404 !signature.optionalParameters.isEmpty) { | |
| 405 StringBuffer buffer = new StringBuffer(); | |
| 406 signature.orderedOptionalParameters.forEach((Element element) { | |
| 407 buffer.write('\$${safeName(element.name)}'); | |
| 408 }); | |
| 409 methodName = '$methodName$buffer'; | |
| 410 } | |
| 411 if (name == closureInvocationSelectorName) return methodName; | |
| 412 return getMappedInstanceName(methodName); | |
| 413 } | |
| 414 | |
| 415 String publicInstanceMethodNameByArity(String name, int arity) { | |
| 416 String newName = operatorNameToIdentifier(name); | |
| 417 if (newName != name) return getMappedOperatorName(newName); | |
| 418 assert(!isPrivateName(name)); | |
| 419 // We don't mangle the closure invoking function name because it | |
| 420 // is generated by string concatenation in applyFunction from | |
| 421 // js_helper.dart. To keep code size down, we potentially shorten | |
| 422 // the prefix though. | |
| 423 if (name == closureInvocationSelectorName) return '$callPrefix\$$arity'; | |
| 424 | |
| 425 return getMappedInstanceName('$name\$$arity'); | |
| 426 } | |
| 427 | |
| 428 String invocationName(Selector selector) { | |
| 429 if (selector.isGetter) { | |
| 430 String proposedName = privateName(selector.library, selector.name); | |
| 431 return '$getterPrefix${getMappedInstanceName(proposedName)}'; | |
| 432 } else if (selector.isSetter) { | |
| 433 String proposedName = privateName(selector.library, selector.name); | |
| 434 return '$setterPrefix${getMappedInstanceName(proposedName)}'; | |
| 435 } else { | |
| 436 String name = selector.name; | |
| 437 if (selector.kind == SelectorKind.OPERATOR | |
| 438 || selector.kind == SelectorKind.INDEX) { | |
| 439 name = operatorNameToIdentifier(name); | |
| 440 assert(name != selector.name); | |
| 441 return getMappedOperatorName(name); | |
| 442 } | |
| 443 assert(name == operatorNameToIdentifier(name)); | |
| 444 StringBuffer buffer = new StringBuffer(); | |
| 445 for (String argumentName in selector.getOrderedNamedArguments()) { | |
| 446 buffer.write('\$${safeName(argumentName)}'); | |
| 447 } | |
| 448 String suffix = '\$${selector.argumentCount}$buffer'; | |
| 449 // We don't mangle the closure invoking function name because it | |
| 450 // is generated by string concatenation in applyFunction from | |
| 451 // js_helper.dart. We potentially shorten the prefix though. | |
| 452 if (selector.isClosureCall) { | |
| 453 return "$callPrefix$suffix"; | |
| 454 } else { | |
| 455 String proposedName = privateName(selector.library, name); | |
| 456 return getMappedInstanceName('$proposedName$suffix'); | |
| 457 } | |
| 458 } | |
| 459 } | |
| 460 | |
| 461 /** | |
| 462 * Returns the internal name used for an invocation mirror of this selector. | |
| 463 */ | |
| 464 String invocationMirrorInternalName(Selector selector) | |
| 465 => invocationName(selector); | |
| 466 | |
| 467 /** | |
| 468 * Returns name of accessor (root to getter and setter) for a static or | |
| 469 * instance field. | |
| 470 */ | |
| 471 String fieldAccessorName(Element element) { | |
| 472 return element.isInstanceMember | |
| 473 ? instanceFieldAccessorName(element) | |
| 474 : getNameOfField(element); | |
| 475 } | |
| 476 | |
| 477 /** | |
| 478 * Returns name of the JavaScript property used to store a static or instance | |
| 479 * field. | |
| 480 */ | |
| 481 String fieldPropertyName(Element element) { | |
| 482 return element.isInstanceMember | |
| 483 ? instanceFieldPropertyName(element) | |
| 484 : getNameOfField(element); | |
| 485 } | |
| 486 | |
| 487 /** | |
| 488 * Returns name of accessor (root to getter and setter) for an instance field. | |
| 489 */ | |
| 490 String instanceFieldAccessorName(Element element) { | |
| 491 String proposedName = privateName(element.library, element.name); | |
| 492 return getMappedInstanceName(proposedName); | |
| 493 } | |
| 494 | |
| 495 String readTypeVariableName(TypeVariableElement element) { | |
| 496 return '\$tv_${instanceFieldAccessorName(element)}'; | |
| 497 } | |
| 498 | |
| 499 /** | |
| 500 * Returns name of the JavaScript property used to store an instance field. | |
| 501 */ | |
| 502 String instanceFieldPropertyName(Element element) { | |
| 503 if (element.hasFixedBackendName) { | |
| 504 return element.fixedBackendName; | |
| 505 } | |
| 506 // If a class is used anywhere as a mixin, we must make the name unique so | |
| 507 // that it does not accidentally shadow. Also, the mixin name must be | |
| 508 // constant over all mixins. | |
| 509 ClassWorld classWorld = compiler.world; | |
| 510 if (classWorld.isUsedAsMixin(element.enclosingClass) || | |
| 511 shadowingAnotherField(element)) { | |
| 512 // Construct a new name for the element based on the library and class it | |
| 513 // is in. The name here is not important, we just need to make sure it is | |
| 514 // unique. If we are minifying, we actually construct the name from the | |
| 515 // minified version of the class name, but the result is minified once | |
| 516 // again, so that is not visible in the end result. | |
| 517 String libraryName = getNameOfLibrary(element.library); | |
| 518 String className = getNameOfClass(element.enclosingClass); | |
| 519 String instanceName = privateName(element.library, element.name); | |
| 520 return getMappedInstanceName('$libraryName\$$className\$$instanceName'); | |
| 521 } | |
| 522 | |
| 523 String proposedName = privateName(element.library, element.name); | |
| 524 return getMappedInstanceName(proposedName); | |
| 525 } | |
| 526 | |
| 527 | |
| 528 bool shadowingAnotherField(Element element) { | |
| 529 return element.enclosingClass.hasFieldShadowedBy(element); | |
| 530 } | |
| 531 | |
| 532 String setterName(Element element) { | |
| 533 // We dynamically create setters from the field-name. The setter name must | |
| 534 // therefore be derived from the instance field-name. | |
| 535 LibraryElement library = element.library; | |
| 536 String name = getMappedInstanceName(privateName(library, element.name)); | |
| 537 return '$setterPrefix$name'; | |
| 538 } | |
| 539 | |
| 540 String setterNameFromAccessorName(String name) { | |
| 541 // We dynamically create setters from the field-name. The setter name must | |
| 542 // therefore be derived from the instance field-name. | |
| 543 return '$setterPrefix$name'; | |
| 544 } | |
| 545 | |
| 546 String getterNameFromAccessorName(String name) { | |
| 547 // We dynamically create getters from the field-name. The getter name must | |
| 548 // therefore be derived from the instance field-name. | |
| 549 return '$getterPrefix$name'; | |
| 550 } | |
| 551 | |
| 552 String getterName(Element element) { | |
| 553 // We dynamically create getters from the field-name. The getter name must | |
| 554 // therefore be derived from the instance field-name. | |
| 555 LibraryElement library = element.library; | |
| 556 String name = getMappedInstanceName(privateName(library, element.name)); | |
| 557 return '$getterPrefix$name'; | |
| 558 } | |
| 559 | |
| 560 String getMappedGlobalName(String proposedName, {bool ensureSafe: true}) { | |
| 561 var newName = globalNameMap[proposedName]; | |
| 562 if (newName == null) { | |
| 563 newName = getFreshName(proposedName, usedGlobalNames, | |
| 564 suggestedGlobalNames, ensureSafe: ensureSafe); | |
| 565 globalNameMap[proposedName] = newName; | |
| 566 } | |
| 567 return newName; | |
| 568 } | |
| 569 | |
| 570 String getMappedInstanceName(String proposedName) { | |
| 571 var newName = instanceNameMap[proposedName]; | |
| 572 if (newName == null) { | |
| 573 newName = getFreshName(proposedName, usedInstanceNames, | |
| 574 suggestedInstanceNames, ensureSafe: true); | |
| 575 instanceNameMap[proposedName] = newName; | |
| 576 } | |
| 577 return newName; | |
| 578 } | |
| 579 | |
| 580 String getMappedOperatorName(String proposedName) { | |
| 581 var newName = operatorNameMap[proposedName]; | |
| 582 if (newName == null) { | |
| 583 newName = getFreshName(proposedName, usedInstanceNames, | |
| 584 suggestedInstanceNames, ensureSafe: false); | |
| 585 operatorNameMap[proposedName] = newName; | |
| 586 } | |
| 587 return newName; | |
| 588 } | |
| 589 | |
| 590 String getFreshName(String proposedName, | |
| 591 Set<String> usedNames, | |
| 592 Map<String, String> suggestedNames, | |
| 593 {bool ensureSafe: true}) { | |
| 594 var candidate; | |
| 595 if (ensureSafe) { | |
| 596 proposedName = safeName(proposedName); | |
| 597 } | |
| 598 assert(!jsReserved.contains(proposedName)); | |
| 599 if (!usedNames.contains(proposedName)) { | |
| 600 candidate = proposedName; | |
| 601 } else { | |
| 602 var counter = popularNameCounters[proposedName]; | |
| 603 var i = counter == null ? 0 : counter; | |
| 604 while (usedNames.contains("$proposedName$i")) { | |
| 605 i++; | |
| 606 } | |
| 607 popularNameCounters[proposedName] = i + 1; | |
| 608 candidate = "$proposedName$i"; | |
| 609 } | |
| 610 usedNames.add(candidate); | |
| 611 return candidate; | |
| 612 } | |
| 613 | |
| 614 String getClosureVariableName(String name, int id) { | |
| 615 return "${name}_$id"; | |
| 616 } | |
| 617 | |
| 618 /** | |
| 619 * Returns a preferred JS-id for the given top-level or static element. | |
| 620 * The returned id is guaranteed to be a valid JS-id. | |
| 621 */ | |
| 622 String _computeGuess(Element element) { | |
| 623 assert(!element.isInstanceMember); | |
| 624 String name; | |
| 625 if (element.isGenerativeConstructor) { | |
| 626 name = "${element.enclosingClass.name}\$" | |
| 627 "${element.name}"; | |
| 628 } else if (element.isFactoryConstructor) { | |
| 629 // TODO(johnniwinther): Change factory name encoding as to not include | |
| 630 // the class-name twice. | |
| 631 String className = element.enclosingClass.name; | |
| 632 name = '${className}_${Elements.reconstructConstructorName(element)}'; | |
| 633 } else if (Elements.isStaticOrTopLevel(element)) { | |
| 634 if (element.isClassMember) { | |
| 635 ClassElement enclosingClass = element.enclosingClass; | |
| 636 name = "${enclosingClass.name}_" | |
| 637 "${element.name}"; | |
| 638 } else { | |
| 639 name = element.name.replaceAll('+', '_'); | |
| 640 } | |
| 641 } else if (element.isLibrary) { | |
| 642 LibraryElement library = element; | |
| 643 name = library.getLibraryOrScriptName(); | |
| 644 if (name.contains('.')) { | |
| 645 // For libraries that have a library tag, we use the last part | |
| 646 // of the fully qualified name as their base name. For all other | |
| 647 // libraries, we use the first part of their filename. | |
| 648 name = library.hasLibraryName() | |
| 649 ? name.substring(name.lastIndexOf('.') + 1) | |
| 650 : name.substring(0, name.indexOf('.')); | |
| 651 } | |
| 652 // The filename based name can contain all kinds of nasty characters. Make | |
| 653 // sure it is an identifier. | |
| 654 if (!IDENTIFIER.hasMatch(name)) { | |
| 655 name = name.replaceAllMapped(NON_IDENTIFIER_CHAR, | |
| 656 (match) => match[0].codeUnitAt(0).toRadixString(16)); | |
| 657 if (!IDENTIFIER.hasMatch(name)) { // e.g. starts with digit. | |
| 658 name = 'lib_$name'; | |
| 659 } | |
| 660 } | |
| 661 } else { | |
| 662 name = element.name; | |
| 663 } | |
| 664 return name; | |
| 665 } | |
| 666 | |
| 667 String getInterceptorSuffix(Iterable<ClassElement> classes) { | |
| 668 String abbreviate(ClassElement cls) { | |
| 669 if (cls == compiler.objectClass) return "o"; | |
| 670 if (cls == backend.jsStringClass) return "s"; | |
| 671 if (cls == backend.jsArrayClass) return "a"; | |
| 672 if (cls == backend.jsDoubleClass) return "d"; | |
| 673 if (cls == backend.jsIntClass) return "i"; | |
| 674 if (cls == backend.jsNumberClass) return "n"; | |
| 675 if (cls == backend.jsNullClass) return "u"; | |
| 676 if (cls == backend.jsBoolClass) return "b"; | |
| 677 if (cls == backend.jsInterceptorClass) return "I"; | |
| 678 return cls.name; | |
| 679 } | |
| 680 List<String> names = classes | |
| 681 .where((cls) => !Elements.isNativeOrExtendsNative(cls)) | |
| 682 .map(abbreviate) | |
| 683 .toList(); | |
| 684 // There is one dispatch mechanism for all native classes. | |
| 685 if (classes.any((cls) => Elements.isNativeOrExtendsNative(cls))) { | |
| 686 names.add("x"); | |
| 687 } | |
| 688 // Sort the names of the classes after abbreviating them to ensure | |
| 689 // the suffix is stable and predictable for the suggested names. | |
| 690 names.sort(); | |
| 691 return names.join(); | |
| 692 } | |
| 693 | |
| 694 String getInterceptorName(Element element, Iterable<ClassElement> classes) { | |
| 695 if (classes.contains(backend.jsInterceptorClass)) { | |
| 696 // If the base Interceptor class is in the set of intercepted classes, we | |
| 697 // need to go through the generic getInterceptorMethod, since any subclass | |
| 698 // of the base Interceptor could match. | |
| 699 return getNameOfInstanceMember(element); | |
| 700 } | |
| 701 String suffix = getInterceptorSuffix(classes); | |
| 702 return getMappedGlobalName("${element.name}\$$suffix"); | |
| 703 } | |
| 704 | |
| 705 String getOneShotInterceptorName(Selector selector, | |
| 706 Iterable<ClassElement> classes) { | |
| 707 // The one-shot name is a global name derived from the invocation name. To | |
| 708 // avoid instability we would like the names to be unique and not clash with | |
| 709 // other global names. | |
| 710 | |
| 711 String root = invocationName(selector); // Is already safe. | |
| 712 | |
| 713 if (classes.contains(backend.jsInterceptorClass)) { | |
| 714 // If the base Interceptor class is in the set of intercepted classes, | |
| 715 // this is the most general specialization which uses the generic | |
| 716 // getInterceptor method. To keep the name short, we add '$' only to | |
| 717 // distinguish from global getters or setters; operators and methods can't | |
| 718 // clash. | |
| 719 // TODO(sra): Find a way to get the simple name when Object is not in the | |
| 720 // set of classes for most general variant, e.g. "$lt$n" could be "$lt". | |
| 721 if (selector.isGetter || selector.isSetter) root = '$root\$'; | |
| 722 return getMappedGlobalName(root, ensureSafe: false); | |
| 723 } else { | |
| 724 String suffix = getInterceptorSuffix(classes); | |
| 725 return getMappedGlobalName("$root\$$suffix", ensureSafe: false); | |
| 726 } | |
| 727 } | |
| 728 | |
| 729 /// Returns the runtime name for [element]. The result is not safe as an id. | |
| 730 String getRuntimeTypeName(Element element) { | |
| 731 if (element == null) return 'dynamic'; | |
| 732 return getNameForRti(element); | |
| 733 } | |
| 734 | |
| 735 /** | |
| 736 * Returns a preferred JS-id for the given element. The returned id is | |
| 737 * guaranteed to be a valid JS-id. Globals and static fields are furthermore | |
| 738 * guaranteed to be unique. | |
| 739 * | |
| 740 * For accessing statics consider calling [elementAccess] instead. | |
| 741 */ | |
| 742 // TODO(ahe): This is an internal method to the Namer (and its subclasses) | |
| 743 // and should not be call from outside. | |
| 744 String getNameX(Element element) { | |
| 745 if (element.isInstanceMember) { | |
| 746 if (element.kind == ElementKind.GENERATIVE_CONSTRUCTOR_BODY | |
| 747 || element.kind == ElementKind.FUNCTION) { | |
| 748 return instanceMethodName(element); | |
| 749 } else if (element.kind == ElementKind.GETTER) { | |
| 750 return getterName(element); | |
| 751 } else if (element.kind == ElementKind.SETTER) { | |
| 752 return setterName(element); | |
| 753 } else if (element.kind == ElementKind.FIELD) { | |
| 754 compiler.internalError(element, | |
| 755 'Use instanceFieldPropertyName or instanceFieldAccessorName.'); | |
| 756 return null; | |
| 757 } else { | |
| 758 compiler.internalError(element, | |
| 759 'getName for bad kind: ${element.kind}.'); | |
| 760 return null; | |
| 761 } | |
| 762 } else { | |
| 763 // Use declaration element to ensure invariant on [globals]. | |
| 764 element = element.declaration; | |
| 765 // Dealing with a top-level or static element. | |
| 766 String cached = globals[element]; | |
| 767 if (cached != null) return cached; | |
| 768 | |
| 769 String guess = _computeGuess(element); | |
| 770 ElementKind kind = element.kind; | |
| 771 if (kind == ElementKind.VARIABLE || | |
| 772 kind == ElementKind.PARAMETER) { | |
| 773 // The name is not guaranteed to be unique. | |
| 774 return safeName(guess); | |
| 775 } | |
| 776 if (kind == ElementKind.GENERATIVE_CONSTRUCTOR || | |
| 777 kind == ElementKind.FUNCTION || | |
| 778 kind == ElementKind.CLASS || | |
| 779 kind == ElementKind.FIELD || | |
| 780 kind == ElementKind.GETTER || | |
| 781 kind == ElementKind.SETTER || | |
| 782 kind == ElementKind.TYPEDEF || | |
| 783 kind == ElementKind.LIBRARY) { | |
| 784 bool fixedName = false; | |
| 785 if (Elements.isInstanceField(element)) { | |
| 786 fixedName = element.hasFixedBackendName; | |
| 787 } | |
| 788 String result = fixedName | |
| 789 ? guess | |
| 790 : getFreshName(guess, usedGlobalNames, suggestedGlobalNames, | |
| 791 ensureSafe: true); | |
| 792 globals[element] = result; | |
| 793 return result; | |
| 794 } | |
| 795 compiler.internalError(element, | |
| 796 'getName for unknown kind: ${element.kind}.'); | |
| 797 return null; | |
| 798 } | |
| 799 } | |
| 800 | |
| 801 String getNameForRti(Element element) => getNameX(element); | |
| 802 | |
| 803 String getNameOfLibrary(LibraryElement library) => getNameX(library); | |
| 804 | |
| 805 String getNameOfClass(ClassElement cls) => getNameX(cls); | |
| 806 | |
| 807 String getNameOfField(VariableElement field) => getNameX(field); | |
| 808 | |
| 809 // TODO(ahe): Remove this method. Use get getNameOfMember instead. | |
| 810 String getNameOfInstanceMember(Element member) => getNameX(member); | |
| 811 | |
| 812 String getNameOfMember(Element member) => getNameX(member); | |
| 813 | |
| 814 String getNameOfGlobalField(VariableElement field) => getNameX(field); | |
| 815 | |
| 816 /// Returns true if [element] is stored on current isolate ('$'). We intend | |
| 817 /// to store only mutable static state in [currentIsolate], constants are | |
| 818 /// stored in 'C', and functions, accessors, classes, etc. are stored in one | |
| 819 /// of the other objects in [reservedGlobalObjectNames]. | |
| 820 bool isPropertyOfCurrentIsolate(Element element) { | |
| 821 // TODO(ahe): Make sure this method's documentation is always true and | |
| 822 // remove the word "intend". | |
| 823 return | |
| 824 // TODO(ahe): Re-write these tests to be positive (so it only returns | |
| 825 // true for static/top-level mutable fields). Right now, a number of | |
| 826 // other elements, such as bound closures also live in [currentIsolate]. | |
| 827 !element.isAccessor && | |
| 828 !element.isClass && | |
| 829 !element.isTypedef && | |
| 830 !element.isConstructor && | |
| 831 !element.isFunction && | |
| 832 !element.isLibrary; | |
| 833 } | |
| 834 | |
| 835 /// Returns [currentIsolate] or one of [reservedGlobalObjectNames]. | |
| 836 String globalObjectFor(Element element) { | |
| 837 if (isPropertyOfCurrentIsolate(element)) return currentIsolate; | |
| 838 LibraryElement library = element.library; | |
| 839 if (library == backend.interceptorsLibrary) return 'J'; | |
| 840 if (library.isInternalLibrary) return 'H'; | |
| 841 if (library.isPlatformLibrary) { | |
| 842 if ('${library.canonicalUri}' == 'dart:html') return 'W'; | |
| 843 return 'P'; | |
| 844 } | |
| 845 return userGlobalObjects[ | |
| 846 library.getLibraryOrScriptName().hashCode % userGlobalObjects.length]; | |
| 847 } | |
| 848 | |
| 849 jsAst.PropertyAccess elementAccess(Element element) { | |
| 850 String name = getNameX(element); | |
| 851 return new jsAst.PropertyAccess.field( | |
| 852 new jsAst.VariableUse(globalObjectFor(element)), | |
| 853 name); | |
| 854 } | |
| 855 | |
| 856 String getLazyInitializerName(Element element) { | |
| 857 assert(Elements.isStaticOrTopLevelField(element)); | |
| 858 return getMappedGlobalName("$getterPrefix${getNameX(element)}"); | |
| 859 } | |
| 860 | |
| 861 String getStaticClosureName(Element element) { | |
| 862 assert(Elements.isStaticOrTopLevelFunction(element)); | |
| 863 return getMappedGlobalName("${getNameX(element)}\$closure"); | |
| 864 } | |
| 865 | |
| 866 jsAst.Expression isolateLazyInitializerAccess(Element element) { | |
| 867 return js('#.#', | |
| 868 [globalObjectFor(element), getLazyInitializerName(element)]); | |
| 869 } | |
| 870 | |
| 871 jsAst.Expression isolateStaticClosureAccess(Element element) { | |
| 872 return js('#.#()', | |
| 873 [globalObjectFor(element), getStaticClosureName(element)]); | |
| 874 } | |
| 875 | |
| 876 // This name is used as part of the name of a TypeConstant | |
| 877 String uniqueNameForTypeConstantElement(Element element) { | |
| 878 // TODO(sra): If we replace the period with an identifier character, | |
| 879 // TypeConstants will have better names in unminified code. | |
| 880 return "${globalObjectFor(element)}.${getNameX(element)}"; | |
| 881 } | |
| 882 | |
| 883 String globalObjectForConstant(ConstantValue constant) => 'C'; | |
| 884 | |
| 885 String operatorIsPrefix() => r'$is'; | |
| 886 | |
| 887 String operatorAsPrefix() => r'$as'; | |
| 888 | |
| 889 String operatorSignature() => r'$signature'; | |
| 890 | |
| 891 String typedefTag() => r'typedef'; | |
| 892 | |
| 893 String functionTypeTag() => r'func'; | |
| 894 | |
| 895 String functionTypeVoidReturnTag() => r'void'; | |
| 896 | |
| 897 String functionTypeReturnTypeTag() => r'ret'; | |
| 898 | |
| 899 String functionTypeRequiredParametersTag() => r'args'; | |
| 900 | |
| 901 String functionTypeOptionalParametersTag() => r'opt'; | |
| 902 | |
| 903 String functionTypeNamedParametersTag() => r'named'; | |
| 904 | |
| 905 Map<FunctionType,String> functionTypeNameMap = | |
| 906 new Map<FunctionType,String>(); | |
| 907 final FunctionTypeNamer functionTypeNamer; | |
| 908 | |
| 909 String getFunctionTypeName(FunctionType functionType) { | |
| 910 return functionTypeNameMap.putIfAbsent(functionType, () { | |
| 911 String proposedName = functionTypeNamer.computeName(functionType); | |
| 912 String freshName = getFreshName(proposedName, usedInstanceNames, | |
| 913 suggestedInstanceNames, ensureSafe: true); | |
| 914 return freshName; | |
| 915 }); | |
| 916 } | |
| 917 | |
| 918 String operatorIsType(DartType type) { | |
| 919 if (type.isFunctionType) { | |
| 920 // TODO(erikcorry): Reduce from $isx to ix when we are minifying. | |
| 921 return '${operatorIsPrefix()}_${getFunctionTypeName(type)}'; | |
| 922 } | |
| 923 return operatorIs(type.element); | |
| 924 } | |
| 925 | |
| 926 String operatorIs(Element element) { | |
| 927 // TODO(erikcorry): Reduce from $isx to ix when we are minifying. | |
| 928 return '${operatorIsPrefix()}${getRuntimeTypeName(element)}'; | |
| 929 } | |
| 930 | |
| 931 /* | |
| 932 * Returns a name that does not clash with reserved JS keywords, | |
| 933 * and also ensures it won't clash with other identifiers. | |
| 934 */ | |
| 935 String _safeName(String name, Set<String> reserved) { | |
| 936 if (reserved.contains(name) || name.startsWith(r'$')) { | |
| 937 name = '\$$name'; | |
| 938 } | |
| 939 assert(!reserved.contains(name)); | |
| 940 return name; | |
| 941 } | |
| 942 | |
| 943 String substitutionName(Element element) { | |
| 944 // TODO(ahe): Creating a string here is unfortunate. It is slow (due to | |
| 945 // string concatenation in the implementation), and may prevent | |
| 946 // segmentation of '$'. | |
| 947 return '${operatorAsPrefix()}${getNameForRti(element)}'; | |
| 948 } | |
| 949 | |
| 950 String safeName(String name) => _safeName(name, jsReserved); | |
| 951 String safeVariableName(String name) => _safeName(name, jsVariableReserved); | |
| 952 | |
| 953 String operatorNameToIdentifier(String name) { | |
| 954 if (name == null) return null; | |
| 955 if (name == '==') { | |
| 956 return r'$eq'; | |
| 957 } else if (name == '~') { | |
| 958 return r'$not'; | |
| 959 } else if (name == '[]') { | |
| 960 return r'$index'; | |
| 961 } else if (name == '[]=') { | |
| 962 return r'$indexSet'; | |
| 963 } else if (name == '*') { | |
| 964 return r'$mul'; | |
| 965 } else if (name == '/') { | |
| 966 return r'$div'; | |
| 967 } else if (name == '%') { | |
| 968 return r'$mod'; | |
| 969 } else if (name == '~/') { | |
| 970 return r'$tdiv'; | |
| 971 } else if (name == '+') { | |
| 972 return r'$add'; | |
| 973 } else if (name == '<<') { | |
| 974 return r'$shl'; | |
| 975 } else if (name == '>>') { | |
| 976 return r'$shr'; | |
| 977 } else if (name == '>=') { | |
| 978 return r'$ge'; | |
| 979 } else if (name == '>') { | |
| 980 return r'$gt'; | |
| 981 } else if (name == '<=') { | |
| 982 return r'$le'; | |
| 983 } else if (name == '<') { | |
| 984 return r'$lt'; | |
| 985 } else if (name == '&') { | |
| 986 return r'$and'; | |
| 987 } else if (name == '^') { | |
| 988 return r'$xor'; | |
| 989 } else if (name == '|') { | |
| 990 return r'$or'; | |
| 991 } else if (name == '-') { | |
| 992 return r'$sub'; | |
| 993 } else if (name == 'unary-') { | |
| 994 return r'$negate'; | |
| 995 } else { | |
| 996 return name; | |
| 997 } | |
| 998 } | |
| 999 | |
| 1000 void forgetElement(Element element) { | |
| 1001 String globalName = globals[element]; | |
| 1002 invariant(element, globalName != null, message: 'No global name.'); | |
| 1003 usedGlobalNames.remove(globalName); | |
| 1004 globals.remove(element); | |
| 1005 } | |
| 1006 } | |
| 1007 | |
| 1008 /** | |
| 1009 * Generator of names for [ConstantValue] values. | |
| 1010 * | |
| 1011 * The names are stable under perturbations of the source. The name is either a | |
| 1012 * short sequence of words, if this can be found from the constant, or a type | |
| 1013 * followed by a hash tag. | |
| 1014 * | |
| 1015 * List_imX // A List, with hash tag. | |
| 1016 * C_Sentinel // const Sentinel(), "C_" added to avoid clash | |
| 1017 * // with class name. | |
| 1018 * JSInt_methods // an interceptor. | |
| 1019 * Duration_16000 // const Duration(milliseconds: 16) | |
| 1020 * EventKeyProvider_keyup // const EventKeyProvider('keyup') | |
| 1021 * | |
| 1022 */ | |
| 1023 class ConstantNamingVisitor implements ConstantValueVisitor { | |
| 1024 | |
| 1025 static final RegExp IDENTIFIER = new RegExp(r'^[A-Za-z_$][A-Za-z0-9_$]*$'); | |
| 1026 static const MAX_FRAGMENTS = 5; | |
| 1027 static const MAX_EXTRA_LENGTH = 30; | |
| 1028 static const DEFAULT_TAG_LENGTH = 3; | |
| 1029 | |
| 1030 final Compiler compiler; | |
| 1031 final ConstantCanonicalHasher hasher; | |
| 1032 | |
| 1033 String root = null; // First word, usually a type name. | |
| 1034 bool failed = false; // Failed to generate something pretty. | |
| 1035 List<String> fragments = <String>[]; | |
| 1036 int length = 0; | |
| 1037 | |
| 1038 ConstantNamingVisitor(this.compiler, this.hasher); | |
| 1039 | |
| 1040 String getName(ConstantValue constant) { | |
| 1041 _visit(constant); | |
| 1042 if (root == null) return 'CONSTANT'; | |
| 1043 if (failed) return '${root}_${getHashTag(constant, DEFAULT_TAG_LENGTH)}'; | |
| 1044 if (fragments.length == 1) return 'C_${root}'; | |
| 1045 return fragments.join('_'); | |
| 1046 } | |
| 1047 | |
| 1048 String getHashTag(ConstantValue constant, int width) => | |
| 1049 hashWord(hasher.getHash(constant), width); | |
| 1050 | |
| 1051 String hashWord(int hash, int length) { | |
| 1052 hash &= 0x1fffffff; | |
| 1053 StringBuffer sb = new StringBuffer(); | |
| 1054 for (int i = 0; i < length; i++) { | |
| 1055 int digit = hash % 62; | |
| 1056 sb.write('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' | |
| 1057 [digit]); | |
| 1058 hash ~/= 62; | |
| 1059 if (hash == 0) break; | |
| 1060 } | |
| 1061 return sb.toString(); | |
| 1062 } | |
| 1063 | |
| 1064 void addRoot(String fragment) { | |
| 1065 if (root == null && fragments.isEmpty) { | |
| 1066 root = fragment; | |
| 1067 } | |
| 1068 add(fragment); | |
| 1069 } | |
| 1070 | |
| 1071 void add(String fragment) { | |
| 1072 assert(fragment.length > 0); | |
| 1073 fragments.add(fragment); | |
| 1074 length += fragment.length; | |
| 1075 if (fragments.length > MAX_FRAGMENTS) failed = true; | |
| 1076 if (root != null && length > root.length + 1 + MAX_EXTRA_LENGTH) { | |
| 1077 failed = true; | |
| 1078 } | |
| 1079 } | |
| 1080 | |
| 1081 void addIdentifier(String fragment) { | |
| 1082 if (fragment.length <= MAX_EXTRA_LENGTH && IDENTIFIER.hasMatch(fragment)) { | |
| 1083 add(fragment); | |
| 1084 } else { | |
| 1085 failed = true; | |
| 1086 } | |
| 1087 } | |
| 1088 | |
| 1089 _visit(ConstantValue constant) { | |
| 1090 return constant.accept(this); | |
| 1091 } | |
| 1092 | |
| 1093 visitFunction(FunctionConstantValue constant) { | |
| 1094 add(constant.element.name); | |
| 1095 } | |
| 1096 | |
| 1097 visitNull(NullConstantValue constant) { | |
| 1098 add('null'); | |
| 1099 } | |
| 1100 | |
| 1101 visitInt(IntConstantValue constant) { | |
| 1102 // No `addRoot` since IntConstants are always inlined. | |
| 1103 if (constant.primitiveValue < 0) { | |
| 1104 add('m${-constant.primitiveValue}'); | |
| 1105 } else { | |
| 1106 add('${constant.primitiveValue}'); | |
| 1107 } | |
| 1108 } | |
| 1109 | |
| 1110 visitDouble(DoubleConstantValue constant) { | |
| 1111 failed = true; | |
| 1112 } | |
| 1113 | |
| 1114 visitTrue(TrueConstantValue constant) { | |
| 1115 add('true'); | |
| 1116 } | |
| 1117 | |
| 1118 visitFalse(FalseConstantValue constant) { | |
| 1119 add('false'); | |
| 1120 } | |
| 1121 | |
| 1122 visitString(StringConstantValue constant) { | |
| 1123 // No `addRoot` since string constants are always inlined. | |
| 1124 addIdentifier(constant.primitiveValue.slowToString()); | |
| 1125 } | |
| 1126 | |
| 1127 visitList(ListConstantValue constant) { | |
| 1128 // TODO(9476): Incorporate type parameters into name. | |
| 1129 addRoot('List'); | |
| 1130 int length = constant.length; | |
| 1131 if (constant.length == 0) { | |
| 1132 add('empty'); | |
| 1133 } else if (length >= MAX_FRAGMENTS) { | |
| 1134 failed = true; | |
| 1135 } else { | |
| 1136 for (int i = 0; i < length; i++) { | |
| 1137 _visit(constant.entries[i]); | |
| 1138 if (failed) break; | |
| 1139 } | |
| 1140 } | |
| 1141 } | |
| 1142 | |
| 1143 visitMap(JavaScriptMapConstant constant) { | |
| 1144 // TODO(9476): Incorporate type parameters into name. | |
| 1145 addRoot('Map'); | |
| 1146 if (constant.length == 0) { | |
| 1147 add('empty'); | |
| 1148 } else { | |
| 1149 // Using some bits from the keys hash tag groups the names Maps with the | |
| 1150 // same structure. | |
| 1151 add(getHashTag(constant.keyList, 2) + getHashTag(constant, 3)); | |
| 1152 } | |
| 1153 } | |
| 1154 | |
| 1155 visitConstructed(ConstructedConstantValue constant) { | |
| 1156 addRoot(constant.type.element.name); | |
| 1157 for (int i = 0; i < constant.fields.length; i++) { | |
| 1158 _visit(constant.fields[i]); | |
| 1159 if (failed) return; | |
| 1160 } | |
| 1161 } | |
| 1162 | |
| 1163 visitType(TypeConstantValue constant) { | |
| 1164 addRoot('Type'); | |
| 1165 DartType type = constant.representedType; | |
| 1166 JavaScriptBackend backend = compiler.backend; | |
| 1167 String name = backend.rti.getTypeRepresentationForTypeConstant(type); | |
| 1168 addIdentifier(name); | |
| 1169 } | |
| 1170 | |
| 1171 visitInterceptor(InterceptorConstantValue constant) { | |
| 1172 addRoot(constant.dispatchedType.element.name); | |
| 1173 add('methods'); | |
| 1174 } | |
| 1175 | |
| 1176 visitDummy(DummyConstantValue constant) { | |
| 1177 add('dummy_receiver'); | |
| 1178 } | |
| 1179 | |
| 1180 visitDeferred(DeferredConstantValue constant) { | |
| 1181 addRoot('Deferred'); | |
| 1182 } | |
| 1183 } | |
| 1184 | |
| 1185 /** | |
| 1186 * Generates canonical hash values for [ConstantValue]s. | |
| 1187 * | |
| 1188 * Unfortunately, [Constant.hashCode] is not stable under minor perturbations, | |
| 1189 * so it can't be used for generating names. This hasher keeps consistency | |
| 1190 * between runs by basing hash values of the names of elements, rather than | |
| 1191 * their hashCodes. | |
| 1192 */ | |
| 1193 class ConstantCanonicalHasher implements ConstantValueVisitor<int> { | |
| 1194 | |
| 1195 static const _MASK = 0x1fffffff; | |
| 1196 static const _UINT32_LIMIT = 4 * 1024 * 1024 * 1024; | |
| 1197 | |
| 1198 | |
| 1199 final Compiler compiler; | |
| 1200 final Map<ConstantValue, int> hashes = new Map<ConstantValue, int>(); | |
| 1201 | |
| 1202 ConstantCanonicalHasher(this.compiler); | |
| 1203 | |
| 1204 int getHash(ConstantValue constant) => _visit(constant); | |
| 1205 | |
| 1206 int _visit(ConstantValue constant) { | |
| 1207 int hash = hashes[constant]; | |
| 1208 if (hash == null) { | |
| 1209 hash = _finish(constant.accept(this)); | |
| 1210 hashes[constant] = hash; | |
| 1211 } | |
| 1212 return hash; | |
| 1213 } | |
| 1214 | |
| 1215 int visitNull(NullConstantValue constant) => 1; | |
| 1216 int visitTrue(TrueConstantValue constant) => 2; | |
| 1217 int visitFalse(FalseConstantValue constant) => 3; | |
| 1218 | |
| 1219 int visitFunction(FunctionConstantValue constant) { | |
| 1220 return _hashString(1, constant.element.name); | |
| 1221 } | |
| 1222 | |
| 1223 int visitInt(IntConstantValue constant) => _hashInt(constant.primitiveValue); | |
| 1224 | |
| 1225 int visitDouble(DoubleConstantValue constant) { | |
| 1226 return _hashDouble(constant.primitiveValue); | |
| 1227 } | |
| 1228 | |
| 1229 int visitString(StringConstantValue constant) { | |
| 1230 return _hashString(2, constant.primitiveValue.slowToString()); | |
| 1231 } | |
| 1232 | |
| 1233 int visitList(ListConstantValue constant) { | |
| 1234 return _hashList(constant.length, constant.entries); | |
| 1235 } | |
| 1236 | |
| 1237 int visitMap(MapConstantValue constant) { | |
| 1238 int hash = _hashList(constant.length, constant.keys); | |
| 1239 return _hashList(hash, constant.values); | |
| 1240 } | |
| 1241 | |
| 1242 int visitConstructed(ConstructedConstantValue constant) { | |
| 1243 int hash = _hashString(3, constant.type.element.name); | |
| 1244 for (int i = 0; i < constant.fields.length; i++) { | |
| 1245 hash = _combine(hash, _visit(constant.fields[i])); | |
| 1246 } | |
| 1247 return hash; | |
| 1248 } | |
| 1249 | |
| 1250 int visitType(TypeConstantValue constant) { | |
| 1251 DartType type = constant.representedType; | |
| 1252 JavaScriptBackend backend = compiler.backend; | |
| 1253 String name = backend.rti.getTypeRepresentationForTypeConstant(type); | |
| 1254 return _hashString(4, name); | |
| 1255 } | |
| 1256 | |
| 1257 visitInterceptor(InterceptorConstantValue constant) { | |
| 1258 String typeName = constant.dispatchedType.element.name; | |
| 1259 return _hashString(5, typeName); | |
| 1260 } | |
| 1261 | |
| 1262 visitDummy(DummyConstantValue constant) { | |
| 1263 compiler.internalError(NO_LOCATION_SPANNABLE, | |
| 1264 'DummyReceiverConstant should never be named and never be subconstant'); | |
| 1265 } | |
| 1266 | |
| 1267 visitDeferred(DeferredConstantValue constant) { | |
| 1268 int hash = constant.prefix.hashCode; | |
| 1269 return _combine(hash, constant.referenced.accept(this)); | |
| 1270 } | |
| 1271 | |
| 1272 int _hashString(int hash, String s) { | |
| 1273 int length = s.length; | |
| 1274 hash = _combine(hash, length); | |
| 1275 // Increasing stride is O(log N) on large strings which are unlikely to have | |
| 1276 // many collisions. | |
| 1277 for (int i = 0; i < length; i += 1 + (i >> 2)) { | |
| 1278 hash = _combine(hash, s.codeUnitAt(i)); | |
| 1279 } | |
| 1280 return hash; | |
| 1281 } | |
| 1282 | |
| 1283 int _hashList(int hash, List<ConstantValue> constants) { | |
| 1284 for (ConstantValue constant in constants) { | |
| 1285 hash = _combine(hash, _visit(constant)); | |
| 1286 } | |
| 1287 return hash; | |
| 1288 } | |
| 1289 | |
| 1290 static int _hashInt(int value) { | |
| 1291 if (value.abs() < _UINT32_LIMIT) return _MASK & value; | |
| 1292 return _hashDouble(value.toDouble()); | |
| 1293 } | |
| 1294 | |
| 1295 static int _hashDouble(double value) { | |
| 1296 double magnitude = value.abs(); | |
| 1297 int sign = value < 0 ? 1 : 0; | |
| 1298 if (magnitude < _UINT32_LIMIT) { // 2^32 | |
| 1299 int intValue = value.toInt(); | |
| 1300 // Integer valued doubles in 32-bit range hash to the same values as ints. | |
| 1301 int hash = _hashInt(intValue); | |
| 1302 if (value == intValue) return hash; | |
| 1303 hash = _combine(hash, sign); | |
| 1304 int fraction = ((magnitude - intValue.abs()) * (_MASK + 1)).toInt(); | |
| 1305 hash = _combine(hash, fraction); | |
| 1306 return hash; | |
| 1307 } else if (value.isInfinite) { | |
| 1308 return _combine(6, sign); | |
| 1309 } else if (value.isNaN) { | |
| 1310 return 7; | |
| 1311 } else { | |
| 1312 int hash = 0; | |
| 1313 while (magnitude >= _UINT32_LIMIT) { | |
| 1314 magnitude = magnitude / _UINT32_LIMIT; | |
| 1315 hash++; | |
| 1316 } | |
| 1317 hash = _combine(hash, sign); | |
| 1318 return _combine(hash, _hashDouble(magnitude)); | |
| 1319 } | |
| 1320 } | |
| 1321 | |
| 1322 /** | |
| 1323 * [_combine] and [_finish] are parts of the [Jenkins hash function][1], | |
| 1324 * modified by using masking to keep values in SMI range. | |
| 1325 * | |
| 1326 * [1]: http://en.wikipedia.org/wiki/Jenkins_hash_function | |
| 1327 */ | |
| 1328 static int _combine(int hash, int value) { | |
| 1329 hash = _MASK & (hash + value); | |
| 1330 hash = _MASK & (hash + (((_MASK >> 10) & hash) << 10)); | |
| 1331 hash = hash ^ (hash >> 6); | |
| 1332 return hash; | |
| 1333 } | |
| 1334 | |
| 1335 static int _finish(int hash) { | |
| 1336 hash = _MASK & (hash + (((_MASK >> 3) & hash) << 3)); | |
| 1337 hash = hash & (hash >> 11); | |
| 1338 return _MASK & (hash + (((_MASK >> 15) & hash) << 15)); | |
| 1339 } | |
| 1340 } | |
| 1341 | |
| 1342 class FunctionTypeNamer extends DartTypeVisitor { | |
| 1343 final Compiler compiler; | |
| 1344 StringBuffer sb; | |
| 1345 | |
| 1346 FunctionTypeNamer(this.compiler); | |
| 1347 | |
| 1348 JavaScriptBackend get backend => compiler.backend; | |
| 1349 | |
| 1350 String computeName(DartType type) { | |
| 1351 sb = new StringBuffer(); | |
| 1352 visit(type); | |
| 1353 return sb.toString(); | |
| 1354 } | |
| 1355 | |
| 1356 visit(DartType type) { | |
| 1357 type.accept(this, null); | |
| 1358 } | |
| 1359 | |
| 1360 visitType(DartType type, _) { | |
| 1361 sb.write(type.name); | |
| 1362 } | |
| 1363 | |
| 1364 visitFunctionType(FunctionType type, _) { | |
| 1365 if (backend.rti.isSimpleFunctionType(type)) { | |
| 1366 sb.write('args${type.parameterTypes.length}'); | |
| 1367 return; | |
| 1368 } | |
| 1369 visit(type.returnType); | |
| 1370 sb.write('_'); | |
| 1371 for (DartType parameter in type.parameterTypes) { | |
| 1372 sb.write('_'); | |
| 1373 visit(parameter); | |
| 1374 } | |
| 1375 bool first = false; | |
| 1376 for (DartType parameter in type.optionalParameterTypes) { | |
| 1377 if (!first) { | |
| 1378 sb.write('_'); | |
| 1379 } | |
| 1380 sb.write('_'); | |
| 1381 visit(parameter); | |
| 1382 first = true; | |
| 1383 } | |
| 1384 if (!type.namedParameterTypes.isEmpty) { | |
| 1385 first = false; | |
| 1386 for (DartType parameter in type.namedParameterTypes) { | |
| 1387 if (!first) { | |
| 1388 sb.write('_'); | |
| 1389 } | |
| 1390 sb.write('_'); | |
| 1391 visit(parameter); | |
| 1392 first = true; | |
| 1393 } | |
| 1394 } | |
| 1395 } | |
| 1396 } | |
| OLD | NEW |