| // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| part of js_backend; |
| |
| /** |
| * Assigns JavaScript identifiers to Dart variables, class-names and members. |
| * |
| * Names are generated through three stages: |
| * |
| * 1. Original names and proposed names |
| * 2. Disambiguated names (also known as "mangled names") |
| * 3. Annotated names |
| * |
| * Original names are names taken directly from the input. |
| * |
| * Proposed names are either original names or synthesized names for input |
| * elements that do not have original names. |
| * |
| * Disambiguated names are derived from the above, but are mangled to ensure |
| * uniqueness within some namespace (e.g. as fields on the same JS object). |
| * In [MinifyNamer], disambiguated names are also minified. |
| * |
| * Annotated names are names generated from a disambiguated name. Annnotated |
| * names must be computable at runtime by prefixing/suffixing constant strings |
| * onto the disambiguated name. |
| * |
| * For example, some entity called `x` might be associated with these names: |
| * |
| * Original name: `x` |
| * |
| * Disambiguated name: `x1` (if something else was called `x`) |
| * |
| * Annotated names: `x1` (field name) |
| * `get$x1` (getter name) |
| * `set$x1` (setter name) |
| * |
| * The [Namer] can choose the disambiguated names, and to some degree the |
| * prefix/suffix constants used to construct annotated names. It cannot choose |
| * annotated names with total freedom, for example, it cannot choose that the |
| * getter for `x1` should be called `getX` -- the annotated names are always |
| * built by concatenation. |
| * |
| * Disambiguated names must be chosen such that none of the annotated names can |
| * clash with each other. This may happen even if the disambiguated names are |
| * distinct, for example, suppose a field `x` and `get$x` exists in the input: |
| * |
| * Original names: `x` and `get$x` |
| * |
| * Disambiguated names: `x` and `get$x` (the two names a different) |
| * |
| * Annotated names: `x` (field for `x`) |
| * `get$x` (getter for `x`) |
| * `get$x` (field for `get$x`) |
| * `get$get$x` (getter for `get$x`) |
| * |
| * The getter for `x` clashes with the field name for `get$x`, so the |
| * disambiguated names are invalid. |
| * |
| * Additionally, disambiguated names must be chosen such that all annotated |
| * names are valid JavaScript identifiers and do not coincide with a native |
| * JavaScript property such as `__proto__`. |
| * |
| * The following annotated names are generated for instance members, where |
| * <NAME> denotes the disambiguated name. |
| * |
| * 0. The disambiguated name can itself be seen as an annotated name. |
| * |
| * 1. Multiple annotated names exist for the `call` method, encoding arity and |
| * named parameters with the pattern: |
| * |
| * call$<N>$namedParam1...$namedParam<M> |
| * |
| * where <N> is the number of parameters (required and optional) and <M> is |
| * the number of named parameters, and namedParam<n> are the names of the |
| * named parameters in alphabetical order. |
| * |
| * Note that the same convention is used for the *proposed name* of other |
| * methods. Thus, for ordinary methods, the suffix becomes embedded in the |
| * disambiguated name (and can be minified), whereas for the 'call' method, |
| * the suffix is an annotation that must be computable at runtime |
| * (and thus cannot be minified). |
| * |
| * Note that the ordering of named parameters is not encapsulated in the |
| * [Namer], and is hardcoded into other components, such as [Element] and |
| * [Selector]. |
| * |
| * 2. The getter/setter for a field: |
| * |
| * get$<NAME> |
| * set$<NAME> |
| * |
| * (The [getterPrefix] and [setterPrefix] are different in [MinifyNamer]). |
| * |
| * 3. The `is` and operator uses the following names: |
| * |
| * $is<NAME> |
| * $as<NAME> |
| * |
| * For local variables, the [Namer] only provides *proposed names*. These names |
| * must be disambiguated elsewhere. |
| */ |
| class Namer { |
| |
| static const List<String> javaScriptKeywords = const <String>[ |
| // These are current keywords. |
| "break", "delete", "function", "return", "typeof", "case", "do", "if", |
| "switch", "var", "catch", "else", "in", "this", "void", "continue", |
| "false", "instanceof", "throw", "while", "debugger", "finally", "new", |
| "true", "with", "default", "for", "null", "try", |
| |
| // These are future keywords. |
| "abstract", "double", "goto", "native", "static", "boolean", "enum", |
| "implements", "package", "super", "byte", "export", "import", "private", |
| "synchronized", "char", "extends", "int", "protected", "throws", |
| "class", "final", "interface", "public", "transient", "const", "float", |
| "long", "short", "volatile" |
| ]; |
| |
| static const List<String> reservedPropertySymbols = |
| const <String>[ |
| "__proto__", "prototype", "constructor", "call", |
| // "use strict" disallows the use of "arguments" and "eval" as |
| // variable names or property names. See ECMA-262, Edition 5.1, |
| // section 11.1.5 (for the property names). |
| "eval", "arguments"]; |
| |
| // Symbols that we might be using in our JS snippets. |
| static const List<String> reservedGlobalSymbols = const <String>[ |
| // Section references are from Ecma-262 |
| // (http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf) |
| |
| // 15.1.1 Value Properties of the Global Object |
| "NaN", "Infinity", "undefined", |
| |
| // 15.1.2 Function Properties of the Global Object |
| "eval", "parseInt", "parseFloat", "isNaN", "isFinite", |
| |
| // 15.1.3 URI Handling Function Properties |
| "decodeURI", "decodeURIComponent", |
| "encodeURI", |
| "encodeURIComponent", |
| |
| // 15.1.4 Constructor Properties of the Global Object |
| "Object", "Function", "Array", "String", "Boolean", "Number", "Date", |
| "RegExp", "Error", "EvalError", "RangeError", "ReferenceError", |
| "SyntaxError", "TypeError", "URIError", |
| |
| // 15.1.5 Other Properties of the Global Object |
| "Math", |
| |
| // 10.1.6 Activation Object |
| "arguments", |
| |
| // B.2 Additional Properties (non-normative) |
| "escape", "unescape", |
| |
| // Window props (https://developer.mozilla.org/en/DOM/window) |
| "applicationCache", "closed", "Components", "content", "controllers", |
| "crypto", "defaultStatus", "dialogArguments", "directories", |
| "document", "frameElement", "frames", "fullScreen", "globalStorage", |
| "history", "innerHeight", "innerWidth", "length", |
| "location", "locationbar", "localStorage", "menubar", |
| "mozInnerScreenX", "mozInnerScreenY", "mozScreenPixelsPerCssPixel", |
| "name", "navigator", "opener", "outerHeight", "outerWidth", |
| "pageXOffset", "pageYOffset", "parent", "personalbar", "pkcs11", |
| "returnValue", "screen", "scrollbars", "scrollMaxX", "scrollMaxY", |
| "self", "sessionStorage", "sidebar", "status", "statusbar", "toolbar", |
| "top", "window", |
| |
| // Window methods (https://developer.mozilla.org/en/DOM/window) |
| "alert", "addEventListener", "atob", "back", "blur", "btoa", |
| "captureEvents", "clearInterval", "clearTimeout", "close", "confirm", |
| "disableExternalCapture", "dispatchEvent", "dump", |
| "enableExternalCapture", "escape", "find", "focus", "forward", |
| "GeckoActiveXObject", "getAttention", "getAttentionWithCycleCount", |
| "getComputedStyle", "getSelection", "home", "maximize", "minimize", |
| "moveBy", "moveTo", "open", "openDialog", "postMessage", "print", |
| "prompt", "QueryInterface", "releaseEvents", "removeEventListener", |
| "resizeBy", "resizeTo", "restore", "routeEvent", "scroll", "scrollBy", |
| "scrollByLines", "scrollByPages", "scrollTo", "setInterval", |
| "setResizeable", "setTimeout", "showModalDialog", "sizeToContent", |
| "stop", "uuescape", "updateCommands", "XPCNativeWrapper", |
| "XPCSafeJSOjbectWrapper", |
| |
| // Mozilla Window event handlers, same cite |
| "onabort", "onbeforeunload", "onchange", "onclick", "onclose", |
| "oncontextmenu", "ondragdrop", "onerror", "onfocus", "onhashchange", |
| "onkeydown", "onkeypress", "onkeyup", "onload", "onmousedown", |
| "onmousemove", "onmouseout", "onmouseover", "onmouseup", |
| "onmozorientation", "onpaint", "onreset", "onresize", "onscroll", |
| "onselect", "onsubmit", "onunload", |
| |
| // Safari Web Content Guide |
| // http://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/SafariWebContent.pdf |
| // WebKit Window member data, from WebKit DOM Reference |
| // (http://developer.apple.com/safari/library/documentation/AppleApplications/Reference/WebKitDOMRef/DOMWindow_idl/Classes/DOMWindow/index.html) |
| "ontouchcancel", "ontouchend", "ontouchmove", "ontouchstart", |
| "ongesturestart", "ongesturechange", "ongestureend", |
| |
| // extra window methods |
| "uneval", |
| |
| // keywords https://developer.mozilla.org/en/New_in_JavaScript_1.7, |
| // https://developer.mozilla.org/en/New_in_JavaScript_1.8.1 |
| "getPrototypeOf", "let", "yield", |
| |
| // "future reserved words" |
| "abstract", "int", "short", "boolean", "interface", "static", "byte", |
| "long", "char", "final", "native", "synchronized", "float", "package", |
| "throws", "goto", "private", "transient", "implements", "protected", |
| "volatile", "double", "public", |
| |
| // IE methods |
| // (http://msdn.microsoft.com/en-us/library/ms535873(VS.85).aspx#) |
| "attachEvent", "clientInformation", "clipboardData", "createPopup", |
| "dialogHeight", "dialogLeft", "dialogTop", "dialogWidth", |
| "onafterprint", "onbeforedeactivate", "onbeforeprint", |
| "oncontrolselect", "ondeactivate", "onhelp", "onresizeend", |
| |
| // Common browser-defined identifiers not defined in ECMAScript |
| "event", "external", "Debug", "Enumerator", "Global", "Image", |
| "ActiveXObject", "VBArray", "Components", |
| |
| // Functions commonly defined on Object |
| "toString", "getClass", "constructor", "prototype", "valueOf", |
| |
| // Client-side JavaScript identifiers |
| "Anchor", "Applet", "Attr", "Canvas", "CanvasGradient", |
| "CanvasPattern", "CanvasRenderingContext2D", "CDATASection", |
| "CharacterData", "Comment", "CSS2Properties", "CSSRule", |
| "CSSStyleSheet", "Document", "DocumentFragment", "DocumentType", |
| "DOMException", "DOMImplementation", "DOMParser", "Element", "Event", |
| "ExternalInterface", "FlashPlayer", "Form", "Frame", "History", |
| "HTMLCollection", "HTMLDocument", "HTMLElement", "IFrame", "Image", |
| "Input", "JSObject", "KeyEvent", "Link", "Location", "MimeType", |
| "MouseEvent", "Navigator", "Node", "NodeList", "Option", "Plugin", |
| "ProcessingInstruction", "Range", "RangeException", "Screen", "Select", |
| "Table", "TableCell", "TableRow", "TableSelection", "Text", "TextArea", |
| "UIEvent", "Window", "XMLHttpRequest", "XMLSerializer", |
| "XPathException", "XPathResult", "XSLTProcessor", |
| |
| // These keywords trigger the loading of the java-plugin. For the |
| // next-generation plugin, this results in starting a new Java process. |
| "java", "Packages", "netscape", "sun", "JavaObject", "JavaClass", |
| "JavaArray", "JavaMember", |
| ]; |
| |
| static const List<String> reservedGlobalObjectNames = const <String>[ |
| "A", |
| "B", |
| "C", // Global object for *C*onstants. |
| "D", |
| "E", |
| "F", |
| "G", |
| "H", // Global object for internal (*H*elper) libraries. |
| // I is used for used for the Isolate function. |
| "J", // Global object for the interceptor library. |
| "K", |
| "L", |
| "M", |
| "N", |
| "O", |
| "P", // Global object for other *P*latform libraries. |
| "Q", |
| "R", |
| "S", |
| "T", |
| "U", |
| "V", |
| "W", // Global object for *W*eb libraries (dart:html). |
| "X", |
| "Y", |
| "Z", |
| ]; |
| |
| static const List<String> reservedGlobalHelperFunctions = const <String>[ |
| "init", |
| "Isolate", |
| ]; |
| |
| static final List<String> userGlobalObjects = |
| new List.from(reservedGlobalObjectNames) |
| ..remove('C') |
| ..remove('H') |
| ..remove('J') |
| ..remove('P') |
| ..remove('W'); |
| |
| Set<String> _jsReserved = null; |
| /// Names that cannot be used by members, top level and static |
| /// methods. |
| Set<String> get jsReserved { |
| if (_jsReserved == null) { |
| _jsReserved = new Set<String>(); |
| _jsReserved.addAll(javaScriptKeywords); |
| _jsReserved.addAll(reservedPropertySymbols); |
| } |
| return _jsReserved; |
| } |
| |
| Set<String> _jsVariableReserved = null; |
| /// Names that cannot be used by local variables and parameters. |
| Set<String> get jsVariableReserved { |
| if (_jsVariableReserved == null) { |
| _jsVariableReserved = new Set<String>(); |
| _jsVariableReserved.addAll(javaScriptKeywords); |
| _jsVariableReserved.addAll(reservedPropertySymbols); |
| _jsVariableReserved.addAll(reservedGlobalSymbols); |
| _jsVariableReserved.addAll(reservedGlobalObjectNames); |
| // 26 letters in the alphabet, 25 not counting I. |
| assert(reservedGlobalObjectNames.length == 25); |
| _jsVariableReserved.addAll(reservedGlobalHelperFunctions); |
| } |
| return _jsVariableReserved; |
| } |
| |
| final String currentIsolate = r'$'; |
| final String getterPrefix = r'get$'; |
| final String setterPrefix = r'set$'; |
| final String superPrefix = r'super$'; |
| final String metadataField = '@'; |
| final String callPrefix = 'call'; |
| final String callCatchAllName = r'call*'; |
| final String callNameField = r'$callName'; |
| final String reflectableField = r'$reflectable'; |
| final String reflectionInfoField = r'$reflectionInfo'; |
| final String reflectionNameField = r'$reflectionName'; |
| final String metadataIndexField = r'$metadataIndex'; |
| final String defaultValuesField = r'$defaultValues'; |
| final String methodsWithOptionalArgumentsField = |
| r'$methodsWithOptionalArguments'; |
| final String deferredAction = r'$deferredAction'; |
| |
| final String classDescriptorProperty = r'^'; |
| final String requiredParameterField = r'$requiredArgCount'; |
| |
| /// The non-minifying namer's [callPrefix] with a dollar after it. |
| static const String _callPrefixDollar = r'call$'; |
| |
| // Name of property in a class description for the native dispatch metadata. |
| final String nativeSpecProperty = '%'; |
| |
| static final RegExp IDENTIFIER = new RegExp(r'^[A-Za-z_$][A-Za-z0-9_$]*$'); |
| static final RegExp NON_IDENTIFIER_CHAR = new RegExp(r'[^A-Za-z_0-9$]'); |
| |
| final Compiler compiler; |
| |
| /// Used disambiguated names in the global namespace, issued by |
| /// [_disambiguateGlobal], and [_disambiguateInternalGlobal]. |
| /// |
| /// Although global names are distributed across a number of global objects, |
| /// (see [globalObjectFor]), we currently use a single namespace for all these |
| /// names. |
| final Set<String> usedGlobalNames = new Set<String>(); |
| final Map<Element, String> userGlobals = <Element, String>{}; |
| final Map<String, String> internalGlobals = <String, String>{}; |
| |
| /// Used disambiguated names in the instance namespace, issued by |
| /// [_disambiguateMember], [_disambiguateInternalMember], |
| /// [_disambiguateOperator], and [reservePublicMemberName]. |
| final Set<String> usedInstanceNames = new Set<String>(); |
| final Map<String, String> userInstanceMembers = <String, String>{}; |
| final Map<Element, String> internalInstanceMembers = <Element, String>{}; |
| final Map<String, String> userInstanceOperators = <String, String>{}; |
| |
| final Map<String, int> popularNameCounters = <String, int>{}; |
| |
| final Map<ConstantValue, String> constantNames = <ConstantValue, String>{}; |
| final Map<ConstantValue, String> constantLongNames = |
| <ConstantValue, String>{}; |
| ConstantCanonicalHasher constantHasher; |
| |
| /// Maps private names to a library that may use that name without prefixing |
| /// itself. Used for building proposed names. |
| final Map<String, LibraryElement> shortPrivateNameOwners = |
| <String, LibraryElement>{}; |
| |
| /// Maps proposed names to *suggested* disambiguated names. |
| /// |
| /// Suggested names are hints to the [MinifyNamer], suggesting that a specific |
| /// names be given to the first item with the given proposed name. |
| /// |
| /// This is currently used in [MinifyNamer] to assign very short minified |
| /// names to things that tend to be used very often. |
| final Map<String, String> suggestedGlobalNames = <String, String>{}; |
| final Map<String, String> suggestedInstanceNames = <String, String>{}; |
| |
| // All alphanumeric characters. |
| static const String _alphaNumeric = |
| 'abcdefghijklmnopqrstuvwxyzABZDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; |
| |
| Namer(Compiler compiler) |
| : compiler = compiler, |
| constantHasher = new ConstantCanonicalHasher(compiler), |
| functionTypeNamer = new FunctionTypeNamer(compiler); |
| |
| JavaScriptBackend get backend => compiler.backend; |
| |
| String get isolateName => 'Isolate'; |
| String get isolatePropertiesName => r'$isolateProperties'; |
| String get noSuchMethodName => publicInstanceMethodNameByArity( |
| Compiler.NO_SUCH_METHOD, Compiler.NO_SUCH_METHOD_ARG_COUNT); |
| /** |
| * Some closures must contain their name. The name is stored in |
| * [STATIC_CLOSURE_NAME_NAME]. |
| */ |
| String get STATIC_CLOSURE_NAME_NAME => r'$name'; |
| String get closureInvocationSelectorName => Compiler.CALL_OPERATOR_NAME; |
| bool get shouldMinify => false; |
| |
| /// Returns the string that is to be used as the result of a call to |
| /// [JS_GET_NAME] at [node] with argument [name]. |
| String getNameForJsGetName(Node node, JsGetName name) { |
| switch (name) { |
| case JsGetName.GETTER_PREFIX: return getterPrefix; |
| case JsGetName.SETTER_PREFIX: return setterPrefix; |
| case JsGetName.CALL_PREFIX: return callPrefix; |
| case JsGetName.CALL_PREFIX0: return '${callPrefix}\$0'; |
| case JsGetName.CALL_PREFIX1: return '${callPrefix}\$1'; |
| case JsGetName.CALL_PREFIX2: return '${callPrefix}\$2'; |
| case JsGetName.CALL_PREFIX3: return '${callPrefix}\$3'; |
| case JsGetName.CALL_CATCH_ALL: return callCatchAllName; |
| case JsGetName.REFLECTABLE: return reflectableField; |
| case JsGetName.CLASS_DESCRIPTOR_PROPERTY: |
| return classDescriptorProperty; |
| case JsGetName.REQUIRED_PARAMETER_PROPERTY: |
| return requiredParameterField; |
| case JsGetName.DEFAULT_VALUES_PROPERTY: return defaultValuesField; |
| case JsGetName.CALL_NAME_PROPERTY: return callNameField; |
| case JsGetName.DEFERRED_ACTION_PROPERTY: return deferredAction; |
| default: |
| compiler.reportError( |
| node, MessageKind.GENERIC, |
| {'text': 'Error: Namer has no name for "$name".'}); |
| return 'BROKEN'; |
| } |
| } |
| |
| /// Disambiguated name for [constant]. |
| /// |
| /// Unique within the global-member namespace. |
| String constantName(ConstantValue constant) { |
| // In the current implementation it doesn't make sense to give names to |
| // function constants since the function-implementation itself serves as |
| // constant and can be accessed directly. |
| assert(!constant.isFunction); |
| String result = constantNames[constant]; |
| if (result == null) { |
| String longName = constantLongName(constant); |
| result = getFreshName(longName, usedGlobalNames, suggestedGlobalNames); |
| constantNames[constant] = result; |
| } |
| return result; |
| } |
| |
| /// Proposed name for [constant]. |
| String constantLongName(ConstantValue constant) { |
| String longName = constantLongNames[constant]; |
| if (longName == null) { |
| longName = new ConstantNamingVisitor(compiler, constantHasher) |
| .getName(constant); |
| constantLongNames[constant] = longName; |
| } |
| return longName; |
| } |
| |
| String breakLabelName(LabelDefinition label) { |
| return '\$${label.labelName}\$${label.target.nestingLevel}'; |
| } |
| |
| String implicitBreakLabelName(JumpTarget target) { |
| return '\$${target.nestingLevel}'; |
| } |
| |
| // We sometimes handle continue targets differently from break targets, |
| // so we have special continue-only labels. |
| String continueLabelName(LabelDefinition label) { |
| return 'c\$${label.labelName}\$${label.target.nestingLevel}'; |
| } |
| |
| String implicitContinueLabelName(JumpTarget target) { |
| return 'c\$${target.nestingLevel}'; |
| } |
| |
| /** |
| * If the [originalName] is not private returns [originalName]. Otherwise |
| * mangles the [originalName] so that each library has its own distinguished |
| * version of the name. |
| * |
| * Although the name is not guaranteed to be unique within any namespace, |
| * clashes are very unlikely in practice. Therefore, it can be used in cases |
| * where uniqueness is nice but not a strict requirement. |
| * |
| * The resulting name is a *proposed name* and is never minified. |
| */ |
| String privateName(Name originalName) { |
| String text = originalName.text; |
| |
| // Public names are easy. |
| if (!originalName.isPrivate) return text; |
| |
| LibraryElement library = originalName.library; |
| |
| // The first library asking for a short private name wins. |
| LibraryElement owner = |
| shortPrivateNameOwners.putIfAbsent(text, () => library); |
| |
| if (owner == library) { |
| return text; |
| } else { |
| // Make sure to return a private name that starts with _ so it |
| // cannot clash with any public names. |
| // The name is still not guaranteed to be unique, since both the library |
| // name and originalName could contain $ symbols. |
| String libraryName = _disambiguateGlobal(library); |
| return '_$libraryName\$${text}'; |
| } |
| } |
| |
| String _proposeNameForConstructorBody(ConstructorBodyElement method) { |
| String name = Elements.reconstructConstructorNameSourceString(method); |
| // We include the method suffix on constructor bodies. It has no purpose, |
| // but this way it produces the same names as previous versions of the |
| // Namer class did. |
| List<String> suffix = callSuffixForSignature(method.functionSignature); |
| return '$name\$${suffix.join(r'$')}'; |
| } |
| |
| /// Annotated name for [method] encoding arity and named parameters. |
| String instanceMethodName(FunctionElement method) { |
| if (method.isGenerativeConstructorBody) { |
| return _disambiguateInternalMember(method, |
| () => _proposeNameForConstructorBody(method)); |
| } |
| return invocationName(new Selector.fromElement(method)); |
| } |
| |
| /// Annotated name for a public method with the given [originalName] |
| /// and [arity] and no named parameters. |
| String publicInstanceMethodNameByArity(String originalName, int arity) { |
| return invocationName(new Selector.call(originalName, null, arity)); |
| } |
| |
| /// Returns the annotated name for a variant of `call`. |
| /// The result has the form: |
| /// |
| /// call$<N>$namedParam1...$namedParam<M> |
| /// |
| /// This name cannot be minified because it is generated by string |
| /// concatenation at runtime, by applyFunction in js_helper.dart. |
| String deriveCallMethodName(List<String> suffix) { |
| // TODO(asgerf): Avoid clashes when named parameters contain $ symbols. |
| return '$callPrefix\$${suffix.join(r'$')}'; |
| } |
| |
| /// The suffix list for the pattern: |
| /// |
| /// $<N>$namedParam1...$namedParam<M> |
| /// |
| /// This is used for the annotated names of `call`, and for the proposed name |
| /// for other instance methods. |
| List<String> callSuffixForStructure(CallStructure callStructure) { |
| List<String> suffixes = ['${callStructure.argumentCount}']; |
| suffixes.addAll(callStructure.getOrderedNamedArguments()); |
| return suffixes; |
| } |
| |
| /// The suffix list for the pattern: |
| /// |
| /// $<N>$namedParam1...$namedParam<M> |
| /// |
| /// This is used for the annotated names of `call`, and for the proposed name |
| /// for other instance methods. |
| List<String> callSuffixForSignature(FunctionSignature sig) { |
| List<String> suffixes = ['${sig.parameterCount}']; |
| if (sig.optionalParametersAreNamed) { |
| for (FormalElement param in sig.orderedOptionalParameters) { |
| suffixes.add(param.name); |
| } |
| } |
| return suffixes; |
| } |
| |
| /// Annotated name for the member being invoked by [selector]. |
| String invocationName(Selector selector) { |
| switch (selector.kind) { |
| case SelectorKind.GETTER: |
| String disambiguatedName = _disambiguateMember(selector.memberName); |
| return deriveGetterName(disambiguatedName); |
| |
| case SelectorKind.SETTER: |
| String disambiguatedName = _disambiguateMember(selector.memberName); |
| return deriveSetterName(disambiguatedName); |
| |
| case SelectorKind.OPERATOR: |
| case SelectorKind.INDEX: |
| String operatorIdentifier = operatorNameToIdentifier(selector.name); |
| String disambiguatedName = _disambiguateOperator(operatorIdentifier); |
| return disambiguatedName; // Operators are not annotated. |
| |
| case SelectorKind.CALL: |
| List<String> suffix = callSuffixForStructure(selector.callStructure); |
| if (selector.name == Compiler.CALL_OPERATOR_NAME) { |
| // Derive the annotated name for this variant of 'call'. |
| return deriveCallMethodName(suffix); |
| } |
| String disambiguatedName = |
| _disambiguateMember(selector.memberName, suffix); |
| return disambiguatedName; // Methods other than call are not annotated. |
| |
| default: |
| compiler.internalError(compiler.currentElement, |
| 'Unexpected selector kind: ${selector.kind}'); |
| return null; |
| } |
| } |
| |
| /** |
| * Returns the internal name used for an invocation mirror of this selector. |
| */ |
| String invocationMirrorInternalName(Selector selector) |
| => invocationName(selector); |
| |
| /** |
| * Returns the disambiguated name for the given field, used for constructing |
| * the getter and setter names. |
| */ |
| String fieldAccessorName(FieldElement element) { |
| return element.isInstanceMember |
| ? _disambiguateMember(element.memberName) |
| : _disambiguateGlobal(element); |
| } |
| |
| /** |
| * Returns name of the JavaScript property used to store a static or instance |
| * field. |
| */ |
| String fieldPropertyName(FieldElement element) { |
| return element.isInstanceMember |
| ? instanceFieldPropertyName(element) |
| : _disambiguateGlobal(element); |
| } |
| |
| /** |
| * Returns name of the JavaScript property used to store the |
| * `readTypeVariable` function for the given type variable. |
| */ |
| String nameForReadTypeVariable(TypeVariableElement element) { |
| return _disambiguateInternalMember(element, () => element.name); |
| } |
| |
| /** |
| * Returns a JavaScript property name used to store [element] on one |
| * of the global objects. |
| * |
| * Should be used together with [globalObjectFor], which denotes the object |
| * on which the returned property name should be used. |
| */ |
| String globalPropertyName(Element element) { |
| return _disambiguateGlobal(element); |
| } |
| |
| /** |
| * Returns the JavaScript property name used to store an instance field. |
| */ |
| String instanceFieldPropertyName(FieldElement element) { |
| ClassElement enclosingClass = element.enclosingClass; |
| |
| if (element.hasFixedBackendName) { |
| // Certain native fields must be given a specific name. Native names must |
| // not contain '$'. We rely on this to avoid clashes. |
| assert(enclosingClass.isNative && |
| !element.fixedBackendName.contains(r'$')); |
| |
| return element.fixedBackendName; |
| } |
| |
| // Instances of BoxFieldElement are special. They are already created with |
| // a unique and safe name. However, as boxes are not really instances of |
| // classes, the usual naming scheme that tries to avoid name clashes with |
| // super classes does not apply. We still do not mark the name as a |
| // fixedBackendName, as we want to allow other namers to do something more |
| // clever with them. |
| if (element is BoxFieldElement) { |
| return element.name; |
| } |
| |
| // If the name of the field might clash with another field, |
| // use a mangled field name to avoid potential clashes. |
| // Note that if the class extends a native class, that native class might |
| // have fields with fixed backend names, so we assume the worst and always |
| // mangle the field names of classes extending native classes. |
| // Methods on such classes are stored on the interceptor, not the instance, |
| // so only fields have the potential to clash with a native property name. |
| ClassWorld classWorld = compiler.world; |
| if (classWorld.isUsedAsMixin(enclosingClass) || |
| _isShadowingSuperField(element) || |
| _isUserClassExtendingNative(enclosingClass)) { |
| String proposeName() => '${enclosingClass.name}_${element.name}'; |
| return _disambiguateInternalMember(element, proposeName); |
| } |
| |
| // No superclass uses the disambiguated name as a property name, so we can |
| // use it for this field. This generates nicer field names since otherwise |
| // the field name would have to be mangled. |
| return _disambiguateMember(element.memberName); |
| } |
| |
| bool _isShadowingSuperField(Element element) { |
| return element.enclosingClass.hasFieldShadowedBy(element); |
| } |
| |
| /// True if [class_] is a non-native class that inherits from a native class. |
| bool _isUserClassExtendingNative(ClassElement class_) { |
| return !class_.isNative && |
| Elements.isNativeOrExtendsNative(class_.superclass); |
| } |
| |
| /// Annotated name for the setter of [element]. |
| String setterForElement(MemberElement element) { |
| // We dynamically create setters from the field-name. The setter name must |
| // therefore be derived from the instance field-name. |
| String name = _disambiguateMember(element.memberName); |
| return deriveSetterName(name); |
| } |
| |
| /// Annotated name for the setter of any member with [disambiguatedName]. |
| String deriveSetterName(String disambiguatedName) { |
| // We dynamically create setters from the field-name. The setter name must |
| // therefore be derived from the instance field-name. |
| return '$setterPrefix$disambiguatedName'; |
| } |
| |
| /// Annotated name for the setter of any member with [disambiguatedName]. |
| String deriveGetterName(String disambiguatedName) { |
| // We dynamically create getters from the field-name. The getter name must |
| // therefore be derived from the instance field-name. |
| return '$getterPrefix$disambiguatedName'; |
| } |
| |
| /// Annotated name for the getter of [element]. |
| String getterForElement(MemberElement element) { |
| // We dynamically create getters from the field-name. The getter name must |
| // therefore be derived from the instance field-name. |
| String name = _disambiguateMember(element.memberName); |
| return deriveGetterName(name); |
| } |
| |
| /// Property name for the getter of an instance member with [originalName]. |
| String getterForMember(Name originalName) { |
| String disambiguatedName = _disambiguateMember(originalName); |
| return deriveGetterName(disambiguatedName); |
| } |
| |
| /// Disambiguated name for a compiler-owned global variable. |
| /// |
| /// The resulting name is unique within the global-member namespace. |
| String _disambiguateInternalGlobal(String name) { |
| String newName = internalGlobals[name]; |
| if (newName == null) { |
| newName = getFreshName(name, usedGlobalNames, suggestedGlobalNames); |
| internalGlobals[name] = newName; |
| } |
| return newName; |
| } |
| |
| /// Returns the property name to use for a compiler-owner global variable, |
| /// i.e. one that does not correspond to any element but is used as a utility |
| /// global by code generation. |
| /// |
| /// [name] functions as both the proposed name for the global, and as a key |
| /// identifying the global. The [name] must not contain `$` symbols, since |
| /// the [Namer] uses those names internally. |
| /// |
| /// This provides an easy mechanism of avoiding a name-clash with user-space |
| /// globals, although the callers of must still take care not to accidentally |
| /// pass in the same [name] for two different internal globals. |
| String internalGlobal(String name) { |
| assert(!name.contains(r'$')); |
| return _disambiguateInternalGlobal(name); |
| } |
| |
| /// Returns the disambiguated name for a top-level or static element. |
| /// |
| /// The resulting name is unique within the global-member namespace. |
| String _disambiguateGlobal(Element element) { |
| // TODO(asgerf): We can reuse more short names if we disambiguate with |
| // a separate namespace for each of the global holder objects. |
| element = element.declaration; |
| String newName = userGlobals[element]; |
| if (newName == null) { |
| String proposedName = _proposeNameForGlobal(element); |
| newName = getFreshName(proposedName, usedGlobalNames, |
| suggestedGlobalNames); |
| userGlobals[element] = newName; |
| } |
| return newName; |
| } |
| |
| /// Returns the disambiguated name for an instance method or field |
| /// with [originalName] in [library]. |
| /// |
| /// [library] may be `null` if [originalName] is known to be public. |
| /// |
| /// This is the name used for deriving property names of accessors (getters |
| /// and setters) and as property name for storing methods and method stubs. |
| /// |
| /// [suffixes] denote an extension of [originalName] to distiguish it from |
| /// other members with that name. These are used to encode the arity and |
| /// named parameters to a method. Disambiguating the same [originalName] with |
| /// different [suffixes] will yield different disambiguated names. |
| /// |
| /// The resulting name, and its associated annotated names, are unique |
| /// to the ([originalName], [suffixes]) pair within the instance-member |
| /// namespace. |
| String _disambiguateMember(Name originalName, |
| [List<String> suffixes = const []]) { |
| // Build a string encoding the library name, if the name is private. |
| String libraryKey = originalName.isPrivate |
| ? _disambiguateGlobal(originalName.library) |
| : ''; |
| |
| // In the unique key, separate the name parts by '@'. |
| // This avoids clashes since the original names cannot contain that symbol. |
| String key = '$libraryKey@${originalName.text}@${suffixes.join('@')}'; |
| String newName = userInstanceMembers[key]; |
| if (newName == null) { |
| String proposedName = privateName(originalName); |
| if (!suffixes.isEmpty) { |
| // In the proposed name, separate the name parts by '$', because the |
| // proposed name must be a valid identifier, but not necessarily unique. |
| proposedName += r'$' + suffixes.join(r'$'); |
| } |
| newName = getFreshName(proposedName, |
| usedInstanceNames, suggestedInstanceNames, |
| sanitizeForAnnotations: true); |
| userInstanceMembers[key] = newName; |
| } |
| return newName; |
| } |
| |
| /// Forces the public instance member with [originalName] to have the given |
| /// [disambiguatedName]. |
| /// |
| /// The [originalName] must not have been disambiguated before, and the |
| /// [disambiguatedName] must not have been used. |
| /// |
| /// Using [_disambiguateMember] with the given [originalName] and no suffixes |
| /// will subsequently return [disambiguatedName]. |
| void reservePublicMemberName(String originalName, |
| String disambiguatedName) { |
| // Build a key that corresponds to the one built in disambiguateMember. |
| String libraryPrefix = ''; // Public names have an empty library prefix. |
| String suffix = ''; // We don't need any suffixes. |
| String key = '$libraryPrefix@$originalName@$suffix'; |
| assert(!userInstanceMembers.containsKey(key)); |
| assert(!usedInstanceNames.contains(disambiguatedName)); |
| userInstanceMembers[key] = disambiguatedName; |
| usedInstanceNames.add(disambiguatedName); |
| } |
| |
| /// Disambiguated name unique to [element]. |
| /// |
| /// This is used as the property name for fields, type variables, |
| /// constructor bodies, and super-accessors. |
| /// |
| /// The resulting name is unique within the instance-member namespace. |
| String _disambiguateInternalMember(Element element, String proposeName()) { |
| String newName = internalInstanceMembers[element]; |
| if (newName == null) { |
| String name = proposeName(); |
| bool mayClashNative = _isUserClassExtendingNative(element.enclosingClass); |
| newName = getFreshName(name, |
| usedInstanceNames, suggestedInstanceNames, |
| sanitizeForAnnotations: true, |
| sanitizeForNatives: mayClashNative); |
| internalInstanceMembers[element] = newName; |
| } |
| return newName; |
| } |
| |
| /// Disambiguated name for the given operator. |
| /// |
| /// [operatorIdentifier] must be the operator's identifier, e.g. |
| /// `$add` and not `+`. |
| /// |
| /// The resulting name is unique within the instance-member namespace. |
| String _disambiguateOperator(String operatorIdentifier) { |
| String newName = userInstanceOperators[operatorIdentifier]; |
| if (newName == null) { |
| newName = getFreshName(operatorIdentifier, usedInstanceNames, |
| suggestedInstanceNames); |
| userInstanceOperators[operatorIdentifier] = newName; |
| } |
| return newName; |
| } |
| |
| /// Returns an unused name. |
| /// |
| /// [proposedName] must be a valid JavaScript identifier. |
| /// |
| /// If [sanitizeForAnnotations] is `true`, then the result is guaranteed not |
| /// to have the form of an annotated name. |
| /// |
| /// If [sanitizeForNatives] it `true`, then the result is guaranteed not to |
| /// clash with a property name on a native object. |
| /// |
| /// Note that [MinifyNamer] overrides this method with one that produces |
| /// minified names. |
| String getFreshName(String proposedName, |
| Set<String> usedNames, |
| Map<String, String> suggestedNames, |
| {bool sanitizeForAnnotations: false, |
| bool sanitizeForNatives: false}) { |
| if (sanitizeForAnnotations) { |
| proposedName = _sanitizeForAnnotations(proposedName); |
| } |
| if (sanitizeForNatives) { |
| proposedName = _sanitizeForNatives(proposedName); |
| } |
| proposedName = _sanitizeForKeywords(proposedName); |
| String candidate; |
| if (!usedNames.contains(proposedName)) { |
| candidate = proposedName; |
| } else { |
| int counter = popularNameCounters[proposedName]; |
| int i = (counter == null) ? 0 : counter; |
| while (usedNames.contains("$proposedName$i")) { |
| i++; |
| } |
| popularNameCounters[proposedName] = i + 1; |
| candidate = "$proposedName$i"; |
| } |
| usedNames.add(candidate); |
| return candidate; |
| } |
| |
| /// Returns a variant of [name] that cannot clash with the annotated |
| /// version of another name, that is, the resulting name can never be returned |
| /// by [deriveGetterName], [deriveSetterName], [deriveCallMethodName], |
| /// [operatorIs], or [substitutionName]. |
| /// |
| /// For example, a name `get$x` would be converted to `$get$x` to ensure it |
| /// cannot clash with the getter for `x`. |
| /// |
| /// We don't want to register all potential annotated names in |
| /// [usedInstanceNames] (there are too many), so we use this step to avoid |
| /// clashes between annotated and unannotated names. |
| String _sanitizeForAnnotations(String name) { |
| // Ensure name does not clash with a getter or setter of another name, |
| // one of the other special names that start with `$`, such as `$is`, |
| // or with one of the `call` stubs, such as `call$1`. |
| assert(this is! MinifyNamer); |
| if (name.startsWith(r'$') || |
| name.startsWith(getterPrefix) || |
| name.startsWith(setterPrefix) || |
| name.startsWith(_callPrefixDollar)) { |
| name = '\$$name'; |
| } |
| return name; |
| } |
| |
| /// Returns a variant of [name] that cannot clash with a native property name |
| /// (e.g. the name of a method on a JS DOM object). |
| /// |
| /// If [name] is not an annotated name, the result will not be an annotated |
| /// name either. |
| String _sanitizeForNatives(String name) { |
| if (!name.contains(r'$')) { |
| // Prepend $$. The result must not coincide with an annotated name. |
| name = '\$\$$name'; |
| } |
| return name; |
| } |
| |
| /** |
| * Returns a proposed name for the given top-level or static element. |
| * The returned id is guaranteed to be a valid JS-id. |
| */ |
| String _proposeNameForGlobal(Element element) { |
| assert(!element.isInstanceMember); |
| String name; |
| if (element.isGenerativeConstructor) { |
| name = "${element.enclosingClass.name}\$" |
| "${element.name}"; |
| } else if (element.isFactoryConstructor) { |
| // TODO(johnniwinther): Change factory name encoding as to not include |
| // the class-name twice. |
| String className = element.enclosingClass.name; |
| name = '${className}_${Elements.reconstructConstructorName(element)}'; |
| } else if (Elements.isStaticOrTopLevel(element)) { |
| if (element.isClassMember) { |
| ClassElement enclosingClass = element.enclosingClass; |
| name = "${enclosingClass.name}_" |
| "${element.name}"; |
| } else { |
| name = element.name.replaceAll('+', '_'); |
| } |
| } else if (element.isLibrary) { |
| LibraryElement library = element; |
| name = library.getLibraryOrScriptName(); |
| if (name.contains('.')) { |
| // For libraries that have a library tag, we use the last part |
| // of the fully qualified name as their base name. For all other |
| // libraries, we use the first part of their filename. |
| name = library.hasLibraryName() |
| ? name.substring(name.lastIndexOf('.') + 1) |
| : name.substring(0, name.indexOf('.')); |
| } |
| // The filename based name can contain all kinds of nasty characters. Make |
| // sure it is an identifier. |
| if (!IDENTIFIER.hasMatch(name)) { |
| name = name.replaceAllMapped(NON_IDENTIFIER_CHAR, |
| (match) => match[0].codeUnitAt(0).toRadixString(16)); |
| if (!IDENTIFIER.hasMatch(name)) { // e.g. starts with digit. |
| name = 'lib_$name'; |
| } |
| } |
| } else { |
| name = element.name; |
| } |
| return name; |
| } |
| |
| String suffixForGetInterceptor(Iterable<ClassElement> classes) { |
| String abbreviate(ClassElement cls) { |
| if (cls == compiler.objectClass) return "o"; |
| if (cls == backend.jsStringClass) return "s"; |
| if (cls == backend.jsArrayClass) return "a"; |
| if (cls == backend.jsDoubleClass) return "d"; |
| if (cls == backend.jsIntClass) return "i"; |
| if (cls == backend.jsNumberClass) return "n"; |
| if (cls == backend.jsNullClass) return "u"; |
| if (cls == backend.jsBoolClass) return "b"; |
| if (cls == backend.jsInterceptorClass) return "I"; |
| return cls.name; |
| } |
| List<String> names = classes |
| .where((cls) => !Elements.isNativeOrExtendsNative(cls)) |
| .map(abbreviate) |
| .toList(); |
| // There is one dispatch mechanism for all native classes. |
| if (classes.any((cls) => Elements.isNativeOrExtendsNative(cls))) { |
| names.add("x"); |
| } |
| // Sort the names of the classes after abbreviating them to ensure |
| // the suffix is stable and predictable for the suggested names. |
| names.sort(); |
| return names.join(); |
| } |
| |
| /// Property name used for `getInterceptor` or one of its specializations. |
| String nameForGetInterceptor(Iterable<ClassElement> classes) { |
| FunctionElement getInterceptor = backend.getInterceptorMethod; |
| if (classes.contains(backend.jsInterceptorClass)) { |
| // If the base Interceptor class is in the set of intercepted classes, we |
| // need to go through the generic getInterceptorMethod, since any subclass |
| // of the base Interceptor could match. |
| // The unspecialized getInterceptor method can also be accessed through |
| // its element, so we treat this as a user-space global instead of an |
| // internal global. |
| return _disambiguateGlobal(getInterceptor); |
| } |
| String suffix = suffixForGetInterceptor(classes); |
| return _disambiguateInternalGlobal("${getInterceptor.name}\$$suffix"); |
| } |
| |
| /// Property name used for the one-shot interceptor method for the given |
| /// [selector] and return-type specialization. |
| String nameForGetOneShotInterceptor(Selector selector, |
| Iterable<ClassElement> classes) { |
| // The one-shot name is a global name derived from the invocation name. To |
| // avoid instability we would like the names to be unique and not clash with |
| // other global names. |
| |
| String root = invocationName(selector); |
| |
| if (classes.contains(backend.jsInterceptorClass)) { |
| // If the base Interceptor class is in the set of intercepted classes, |
| // this is the most general specialization which uses the generic |
| // getInterceptor method. To keep the name short, we add '$' only to |
| // distinguish from internal globals requested from outside the Namer |
| // with internalGlobal(). |
| // TODO(sra): Find a way to get the simple name when Object is not in the |
| // set of classes for most general variant, e.g. "$lt$n" could be "$lt". |
| if (selector.isGetter || selector.isSetter) root = '$root\$'; |
| return _disambiguateInternalGlobal(root); |
| } else { |
| String suffix = suffixForGetInterceptor(classes); |
| return _disambiguateInternalGlobal("$root\$$suffix"); |
| } |
| } |
| |
| /// Returns the runtime name for [element]. |
| /// |
| /// This name is used as the basis for deriving `is` and `as` property names |
| /// for the given type. |
| /// |
| /// The result is not always safe as a property name unless prefixing |
| /// [operatorIsPrefix] or [operatorAsPrefix]. If this is a function type, |
| /// then by convention, an underscore must also separate [operatorIsPrefix] |
| /// from the type name. |
| String runtimeTypeName(TypeDeclarationElement element) { |
| if (element == null) return 'dynamic'; |
| // The returned name affects both the global and instance member namespaces: |
| // |
| // - If given a class, this must coincide with the class name, which |
| // is also the GLOBAL property name of its constructor. |
| // |
| // - The result is used to derive `$isX` and `$asX` names, which are used |
| // as INSTANCE property names. |
| // |
| // To prevent clashes in both namespaces at once, we disambiguate the name |
| // as a global here, and in [_sanitizeForAnnotations] we ensure that |
| // ordinary instance members cannot start with `$is` or `$as`. |
| return _disambiguateGlobal(element); |
| } |
| |
| /// Returns the disambiguated name of [class_]. |
| /// |
| /// This is both the *runtime type* of the class (see [runtimeTypeName]) |
| /// and a global property name in which to store its JS constructor. |
| String className(ClassElement class_) => _disambiguateGlobal(class_); |
| |
| /// Property name on which [member] can be accessed directly, |
| /// without clashing with another JS property name. |
| /// |
| /// This is used for implementing super-calls, where ordinary dispatch |
| /// semantics must be circumvented. For example: |
| /// |
| /// class A { foo() } |
| /// class B extends A { |
| /// foo() { super.foo() } |
| /// } |
| /// |
| /// Example translation to JS: |
| /// |
| /// A.prototype.super$A$foo = function() {...} |
| /// A.prototype.foo$0 = A.prototype.super$A$foo |
| /// |
| /// B.prototype.foo$0 = function() { |
| /// this.super$A$foo(); // super.foo() |
| /// } |
| /// |
| String aliasedSuperMemberPropertyName(Element member) { |
| assert(!member.isField); // Fields do not need super aliases. |
| String methodName = instanceMethodName(member); |
| return _disambiguateInternalMember(member, |
| () => 'super\$${member.enclosingClass.name}\$$methodName'); |
| } |
| |
| /// Property name in which to store the given static or instance [method]. |
| /// For instance methods, this includes the suffix encoding arity and named |
| /// parameters. |
| /// |
| /// The name is not necessarily unique to [method], since a static method |
| /// may share its name with an instance method. |
| String methodPropertyName(Element method) { |
| return method.isInstanceMember |
| ? instanceMethodName(method) |
| : globalPropertyName(method); |
| } |
| |
| /// Returns true if [element] is stored on current isolate ('$'). We intend |
| /// to store only mutable static state in [currentIsolate], constants are |
| /// stored in 'C', and functions, accessors, classes, etc. are stored in one |
| /// of the other objects in [reservedGlobalObjectNames]. |
| bool isPropertyOfCurrentIsolate(Element element) { |
| // TODO(ahe): Make sure this method's documentation is always true and |
| // remove the word "intend". |
| return |
| // TODO(ahe): Re-write these tests to be positive (so it only returns |
| // true for static/top-level mutable fields). Right now, a number of |
| // other elements, such as bound closures also live in [currentIsolate]. |
| !element.isAccessor && |
| !element.isClass && |
| !element.isTypedef && |
| !element.isConstructor && |
| !element.isFunction && |
| !element.isLibrary; |
| } |
| |
| /// Returns [currentIsolate] or one of [reservedGlobalObjectNames]. |
| String globalObjectFor(Element element) { |
| if (isPropertyOfCurrentIsolate(element)) return currentIsolate; |
| LibraryElement library = element.library; |
| if (library == backend.interceptorsLibrary) return 'J'; |
| if (library.isInternalLibrary) return 'H'; |
| if (library.isPlatformLibrary) { |
| if ('${library.canonicalUri}' == 'dart:html') return 'W'; |
| return 'P'; |
| } |
| return userGlobalObjects[ |
| library.getLibraryOrScriptName().hashCode % userGlobalObjects.length]; |
| } |
| |
| String lazyInitializerName(Element element) { |
| assert(Elements.isStaticOrTopLevelField(element)); |
| String name = _disambiguateGlobal(element); |
| return _disambiguateInternalGlobal("$getterPrefix$name"); |
| } |
| |
| String staticClosureName(Element element) { |
| assert(Elements.isStaticOrTopLevelFunction(element)); |
| String name = _disambiguateGlobal(element); |
| return _disambiguateInternalGlobal("$name\$closure"); |
| } |
| |
| // This name is used as part of the name of a TypeConstant |
| String uniqueNameForTypeConstantElement(Element element) { |
| // TODO(sra): If we replace the period with an identifier character, |
| // TypeConstants will have better names in unminified code. |
| return "${globalObjectFor(element)}.${globalPropertyName(element)}"; |
| } |
| |
| String globalObjectForConstant(ConstantValue constant) => 'C'; |
| |
| String get operatorIsPrefix => r'$is'; |
| |
| String get operatorAsPrefix => r'$as'; |
| |
| String get operatorSignature => r'$signature'; |
| |
| String get typedefTag => r'typedef'; |
| |
| String get functionTypeTag => r'func'; |
| |
| String get functionTypeVoidReturnTag => r'void'; |
| |
| String get functionTypeReturnTypeTag => r'ret'; |
| |
| String get functionTypeRequiredParametersTag => r'args'; |
| |
| String get functionTypeOptionalParametersTag => r'opt'; |
| |
| String get functionTypeNamedParametersTag => r'named'; |
| |
| Map<FunctionType,String> functionTypeNameMap = |
| new Map<FunctionType,String>(); |
| final FunctionTypeNamer functionTypeNamer; |
| |
| String getFunctionTypeName(FunctionType functionType) { |
| return functionTypeNameMap.putIfAbsent(functionType, () { |
| String proposedName = functionTypeNamer.computeName(functionType); |
| String freshName = getFreshName(proposedName, usedInstanceNames, |
| suggestedInstanceNames); |
| return freshName; |
| }); |
| } |
| |
| String operatorIsType(DartType type) { |
| if (type.isFunctionType) { |
| // TODO(erikcorry): Reduce from $isx to ix when we are minifying. |
| return '${operatorIsPrefix}_${getFunctionTypeName(type)}'; |
| } |
| return operatorIs(type.element); |
| } |
| |
| String operatorIs(ClassElement element) { |
| // TODO(erikcorry): Reduce from $isx to ix when we are minifying. |
| return '${operatorIsPrefix}${runtimeTypeName(element)}'; |
| } |
| |
| /// Returns a name that does not clash with reserved JS keywords. |
| String _sanitizeForKeywords(String name) { |
| if (jsReserved.contains(name)) { |
| name = '\$$name'; |
| } |
| assert(!jsReserved.contains(name)); |
| return name; |
| } |
| |
| String substitutionName(Element element) { |
| return '${operatorAsPrefix}${runtimeTypeName(element)}'; |
| } |
| |
| /// Returns a variable name that cannot clash with a keyword, a global |
| /// variable, or any name starting with a single '$'. |
| /// |
| /// Furthermore, this function is injective, that is, it never returns the |
| /// same name for two different inputs. |
| String safeVariableName(String name) { |
| if (jsVariableReserved.contains(name) || name.startsWith(r'$')) { |
| return '\$$name'; |
| } |
| return name; |
| } |
| |
| String operatorNameToIdentifier(String name) { |
| if (name == null) return null; |
| if (name == '==') { |
| return r'$eq'; |
| } else if (name == '~') { |
| return r'$not'; |
| } else if (name == '[]') { |
| return r'$index'; |
| } else if (name == '[]=') { |
| return r'$indexSet'; |
| } else if (name == '*') { |
| return r'$mul'; |
| } else if (name == '/') { |
| return r'$div'; |
| } else if (name == '%') { |
| return r'$mod'; |
| } else if (name == '~/') { |
| return r'$tdiv'; |
| } else if (name == '+') { |
| return r'$add'; |
| } else if (name == '<<') { |
| return r'$shl'; |
| } else if (name == '>>') { |
| return r'$shr'; |
| } else if (name == '>=') { |
| return r'$ge'; |
| } else if (name == '>') { |
| return r'$gt'; |
| } else if (name == '<=') { |
| return r'$le'; |
| } else if (name == '<') { |
| return r'$lt'; |
| } else if (name == '&') { |
| return r'$and'; |
| } else if (name == '^') { |
| return r'$xor'; |
| } else if (name == '|') { |
| return r'$or'; |
| } else if (name == '-') { |
| return r'$sub'; |
| } else if (name == 'unary-') { |
| return r'$negate'; |
| } else { |
| return name; |
| } |
| } |
| |
| String get incrementalHelperName => r'$dart_unsafe_incremental_support'; |
| |
| jsAst.Expression get accessIncrementalHelper { |
| return js('self.${incrementalHelperName}'); |
| } |
| |
| void forgetElement(Element element) { |
| String globalName = userGlobals[element]; |
| invariant(element, globalName != null, message: 'No global name.'); |
| usedGlobalNames.remove(globalName); |
| userGlobals.remove(element); |
| } |
| } |
| |
| /** |
| * Generator of names for [ConstantValue] values. |
| * |
| * The names are stable under perturbations of the source. The name is either a |
| * short sequence of words, if this can be found from the constant, or a type |
| * followed by a hash tag. |
| * |
| * List_imX // A List, with hash tag. |
| * C_Sentinel // const Sentinel(), "C_" added to avoid clash |
| * // with class name. |
| * JSInt_methods // an interceptor. |
| * Duration_16000 // const Duration(milliseconds: 16) |
| * EventKeyProvider_keyup // const EventKeyProvider('keyup') |
| * |
| */ |
| class ConstantNamingVisitor implements ConstantValueVisitor { |
| |
| static final RegExp IDENTIFIER = new RegExp(r'^[A-Za-z_$][A-Za-z0-9_$]*$'); |
| static const MAX_FRAGMENTS = 5; |
| static const MAX_EXTRA_LENGTH = 30; |
| static const DEFAULT_TAG_LENGTH = 3; |
| |
| final Compiler compiler; |
| final ConstantCanonicalHasher hasher; |
| |
| String root = null; // First word, usually a type name. |
| bool failed = false; // Failed to generate something pretty. |
| List<String> fragments = <String>[]; |
| int length = 0; |
| |
| ConstantNamingVisitor(this.compiler, this.hasher); |
| |
| String getName(ConstantValue constant) { |
| _visit(constant); |
| if (root == null) return 'CONSTANT'; |
| if (failed) return '${root}_${getHashTag(constant, DEFAULT_TAG_LENGTH)}'; |
| if (fragments.length == 1) return 'C_${root}'; |
| return fragments.join('_'); |
| } |
| |
| String getHashTag(ConstantValue constant, int width) => |
| hashWord(hasher.getHash(constant), width); |
| |
| String hashWord(int hash, int length) { |
| hash &= 0x1fffffff; |
| StringBuffer sb = new StringBuffer(); |
| for (int i = 0; i < length; i++) { |
| int digit = hash % 62; |
| sb.write('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' |
| [digit]); |
| hash ~/= 62; |
| if (hash == 0) break; |
| } |
| return sb.toString(); |
| } |
| |
| void addRoot(String fragment) { |
| if (root == null && fragments.isEmpty) { |
| root = fragment; |
| } |
| add(fragment); |
| } |
| |
| void add(String fragment) { |
| assert(fragment.length > 0); |
| fragments.add(fragment); |
| length += fragment.length; |
| if (fragments.length > MAX_FRAGMENTS) failed = true; |
| if (root != null && length > root.length + 1 + MAX_EXTRA_LENGTH) { |
| failed = true; |
| } |
| } |
| |
| void addIdentifier(String fragment) { |
| if (fragment.length <= MAX_EXTRA_LENGTH && IDENTIFIER.hasMatch(fragment)) { |
| add(fragment); |
| } else { |
| failed = true; |
| } |
| } |
| |
| void _visit(ConstantValue constant) { |
| constant.accept(this, null); |
| } |
| |
| @override |
| void visitFunction(FunctionConstantValue constant, [_]) { |
| add(constant.element.name); |
| } |
| |
| @override |
| void visitNull(NullConstantValue constant, [_]) { |
| add('null'); |
| } |
| |
| @override |
| void visitInt(IntConstantValue constant, [_]) { |
| // No `addRoot` since IntConstants are always inlined. |
| if (constant.primitiveValue < 0) { |
| add('m${-constant.primitiveValue}'); |
| } else { |
| add('${constant.primitiveValue}'); |
| } |
| } |
| |
| @override |
| void visitDouble(DoubleConstantValue constant, [_]) { |
| failed = true; |
| } |
| |
| @override |
| void visitBool(BoolConstantValue constant, [_]) { |
| add(constant.isTrue ? 'true' : 'false'); |
| } |
| |
| @override |
| void visitString(StringConstantValue constant, [_]) { |
| // No `addRoot` since string constants are always inlined. |
| addIdentifier(constant.primitiveValue.slowToString()); |
| } |
| |
| @override |
| void visitList(ListConstantValue constant, [_]) { |
| // TODO(9476): Incorporate type parameters into name. |
| addRoot('List'); |
| int length = constant.length; |
| if (constant.length == 0) { |
| add('empty'); |
| } else if (length >= MAX_FRAGMENTS) { |
| failed = true; |
| } else { |
| for (int i = 0; i < length; i++) { |
| _visit(constant.entries[i]); |
| if (failed) break; |
| } |
| } |
| } |
| |
| @override |
| void visitMap(JavaScriptMapConstant constant, [_]) { |
| // TODO(9476): Incorporate type parameters into name. |
| addRoot('Map'); |
| if (constant.length == 0) { |
| add('empty'); |
| } else { |
| // Using some bits from the keys hash tag groups the names Maps with the |
| // same structure. |
| add(getHashTag(constant.keyList, 2) + getHashTag(constant, 3)); |
| } |
| } |
| |
| @override |
| void visitConstructed(ConstructedConstantValue constant, [_]) { |
| addRoot(constant.type.element.name); |
| for (int i = 0; i < constant.fields.length; i++) { |
| _visit(constant.fields[i]); |
| if (failed) return; |
| } |
| } |
| |
| @override |
| void visitType(TypeConstantValue constant, [_]) { |
| addRoot('Type'); |
| DartType type = constant.representedType; |
| JavaScriptBackend backend = compiler.backend; |
| String name = backend.rti.getTypeRepresentationForTypeConstant(type); |
| addIdentifier(name); |
| } |
| |
| @override |
| void visitInterceptor(InterceptorConstantValue constant, [_]) { |
| addRoot(constant.dispatchedType.element.name); |
| add('methods'); |
| } |
| |
| @override |
| void visitDummy(DummyConstantValue constant, [_]) { |
| add('dummy_receiver'); |
| } |
| |
| @override |
| void visitDeferred(DeferredConstantValue constant, [_]) { |
| addRoot('Deferred'); |
| } |
| } |
| |
| /** |
| * Generates canonical hash values for [ConstantValue]s. |
| * |
| * Unfortunately, [Constant.hashCode] is not stable under minor perturbations, |
| * so it can't be used for generating names. This hasher keeps consistency |
| * between runs by basing hash values of the names of elements, rather than |
| * their hashCodes. |
| */ |
| class ConstantCanonicalHasher implements ConstantValueVisitor<int, Null> { |
| |
| static const _MASK = 0x1fffffff; |
| static const _UINT32_LIMIT = 4 * 1024 * 1024 * 1024; |
| |
| |
| final Compiler compiler; |
| final Map<ConstantValue, int> hashes = new Map<ConstantValue, int>(); |
| |
| ConstantCanonicalHasher(this.compiler); |
| |
| int getHash(ConstantValue constant) => _visit(constant); |
| |
| int _visit(ConstantValue constant) { |
| int hash = hashes[constant]; |
| if (hash == null) { |
| hash = _finish(constant.accept(this, null)); |
| hashes[constant] = hash; |
| } |
| return hash; |
| } |
| |
| @override |
| int visitNull(NullConstantValue constant, [_]) => 1; |
| |
| @override |
| int visitBool(BoolConstantValue constant, [_]) { |
| return constant.isTrue ? 2 : 3; |
| } |
| |
| @override |
| int visitFunction(FunctionConstantValue constant, [_]) { |
| return _hashString(1, constant.element.name); |
| } |
| |
| @override |
| int visitInt(IntConstantValue constant, [_]) { |
| return _hashInt(constant.primitiveValue); |
| } |
| |
| @override |
| int visitDouble(DoubleConstantValue constant, [_]) { |
| return _hashDouble(constant.primitiveValue); |
| } |
| |
| @override |
| int visitString(StringConstantValue constant, [_]) { |
| return _hashString(2, constant.primitiveValue.slowToString()); |
| } |
| |
| @override |
| int visitList(ListConstantValue constant, [_]) { |
| return _hashList(constant.length, constant.entries); |
| } |
| |
| @override |
| int visitMap(MapConstantValue constant, [_]) { |
| int hash = _hashList(constant.length, constant.keys); |
| return _hashList(hash, constant.values); |
| } |
| |
| @override |
| int visitConstructed(ConstructedConstantValue constant, [_]) { |
| int hash = _hashString(3, constant.type.element.name); |
| for (int i = 0; i < constant.fields.length; i++) { |
| hash = _combine(hash, _visit(constant.fields[i])); |
| } |
| return hash; |
| } |
| |
| @override |
| int visitType(TypeConstantValue constant, [_]) { |
| DartType type = constant.representedType; |
| JavaScriptBackend backend = compiler.backend; |
| String name = backend.rti.getTypeRepresentationForTypeConstant(type); |
| return _hashString(4, name); |
| } |
| |
| @override |
| int visitInterceptor(InterceptorConstantValue constant, [_]) { |
| String typeName = constant.dispatchedType.element.name; |
| return _hashString(5, typeName); |
| } |
| |
| @override |
| visitDummy(DummyConstantValue constant, [_]) { |
| compiler.internalError(NO_LOCATION_SPANNABLE, |
| 'DummyReceiverConstant should never be named and never be subconstant'); |
| } |
| |
| @override |
| int visitDeferred(DeferredConstantValue constant, [_]) { |
| int hash = constant.prefix.hashCode; |
| return _combine(hash, _visit(constant.referenced)); |
| } |
| |
| int _hashString(int hash, String s) { |
| int length = s.length; |
| hash = _combine(hash, length); |
| // Increasing stride is O(log N) on large strings which are unlikely to have |
| // many collisions. |
| for (int i = 0; i < length; i += 1 + (i >> 2)) { |
| hash = _combine(hash, s.codeUnitAt(i)); |
| } |
| return hash; |
| } |
| |
| int _hashList(int hash, List<ConstantValue> constants) { |
| for (ConstantValue constant in constants) { |
| hash = _combine(hash, _visit(constant)); |
| } |
| return hash; |
| } |
| |
| static int _hashInt(int value) { |
| if (value.abs() < _UINT32_LIMIT) return _MASK & value; |
| return _hashDouble(value.toDouble()); |
| } |
| |
| static int _hashDouble(double value) { |
| double magnitude = value.abs(); |
| int sign = value < 0 ? 1 : 0; |
| if (magnitude < _UINT32_LIMIT) { // 2^32 |
| int intValue = value.toInt(); |
| // Integer valued doubles in 32-bit range hash to the same values as ints. |
| int hash = _hashInt(intValue); |
| if (value == intValue) return hash; |
| hash = _combine(hash, sign); |
| int fraction = ((magnitude - intValue.abs()) * (_MASK + 1)).toInt(); |
| hash = _combine(hash, fraction); |
| return hash; |
| } else if (value.isInfinite) { |
| return _combine(6, sign); |
| } else if (value.isNaN) { |
| return 7; |
| } else { |
| int hash = 0; |
| while (magnitude >= _UINT32_LIMIT) { |
| magnitude = magnitude / _UINT32_LIMIT; |
| hash++; |
| } |
| hash = _combine(hash, sign); |
| return _combine(hash, _hashDouble(magnitude)); |
| } |
| } |
| |
| /** |
| * [_combine] and [_finish] are parts of the [Jenkins hash function][1], |
| * modified by using masking to keep values in SMI range. |
| * |
| * [1]: http://en.wikipedia.org/wiki/Jenkins_hash_function |
| */ |
| static int _combine(int hash, int value) { |
| hash = _MASK & (hash + value); |
| hash = _MASK & (hash + (((_MASK >> 10) & hash) << 10)); |
| hash = hash ^ (hash >> 6); |
| return hash; |
| } |
| |
| static int _finish(int hash) { |
| hash = _MASK & (hash + (((_MASK >> 3) & hash) << 3)); |
| hash = hash & (hash >> 11); |
| return _MASK & (hash + (((_MASK >> 15) & hash) << 15)); |
| } |
| } |
| |
| class FunctionTypeNamer extends DartTypeVisitor { |
| final Compiler compiler; |
| StringBuffer sb; |
| |
| FunctionTypeNamer(this.compiler); |
| |
| JavaScriptBackend get backend => compiler.backend; |
| |
| String computeName(DartType type) { |
| sb = new StringBuffer(); |
| visit(type); |
| return sb.toString(); |
| } |
| |
| visit(DartType type) { |
| type.accept(this, null); |
| } |
| |
| visitType(DartType type, _) { |
| sb.write(type.name); |
| } |
| |
| visitFunctionType(FunctionType type, _) { |
| if (backend.rti.isSimpleFunctionType(type)) { |
| sb.write('args${type.parameterTypes.length}'); |
| return; |
| } |
| visit(type.returnType); |
| sb.write('_'); |
| for (DartType parameter in type.parameterTypes) { |
| sb.write('_'); |
| visit(parameter); |
| } |
| bool first = false; |
| for (DartType parameter in type.optionalParameterTypes) { |
| if (!first) { |
| sb.write('_'); |
| } |
| sb.write('_'); |
| visit(parameter); |
| first = true; |
| } |
| if (!type.namedParameterTypes.isEmpty) { |
| first = false; |
| for (DartType parameter in type.namedParameterTypes) { |
| if (!first) { |
| sb.write('_'); |
| } |
| sb.write('_'); |
| visit(parameter); |
| first = true; |
| } |
| } |
| } |
| } |