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 |