blob: 0b4d5db419f28c07696d03bc887ce73dd4e6e1f6 [file] [log] [blame]
// 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.
library js_backend.namer;
import 'dart:collection' show HashMap;
import 'package:front_end/src/fasta/scanner/characters.dart';
import 'package:js_runtime/shared/embedded_names.dart' show JsGetName;
import '../closure.dart';
import '../common.dart';
import '../common/names.dart' show Identifiers, Names, Selectors;
import '../constants/values.dart';
import '../common_elements.dart' show CommonElements, ElementEnvironment;
import '../diagnostics/invariant.dart' show DEBUG_MODE;
import '../elements/entities.dart';
import '../elements/entity_utils.dart' as utils;
import '../elements/jumps.dart';
import '../elements/names.dart';
import '../elements/types.dart';
import '../js/js.dart' as jsAst;
import '../js_model/closure.dart';
import '../js_model/elements.dart' show JGeneratorBody;
import '../universe/call_structure.dart' show CallStructure;
import '../universe/selector.dart' show Selector, SelectorKind;
import '../universe/world_builder.dart' show CodegenWorldBuilder;
import '../util/util.dart';
import '../world.dart' show JClosedWorld;
import 'backend.dart';
import 'constant_system_javascript.dart';
import 'native_data.dart';
import 'runtime_types.dart';
part 'field_naming_mixin.dart';
part 'frequency_namer.dart';
part 'minify_namer.dart';
part 'namer_names.dart';
/**
* 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. Annotated
* 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>[
// ES5 7.6.1.1 Keywords.
'break',
'do',
'instanceof',
'typeof',
'case',
'else',
'new',
'var',
'catch',
'finally',
'return',
'void',
'continue',
'for',
'switch',
'while',
'debugger',
'function',
'this',
'with',
'default',
'if',
'throw',
'delete',
'in',
'try',
// ES5 7.6.1.2 Future Reserved Words.
'class',
'enum',
'extends',
'super',
'const',
'export',
'import',
// ES5 7.6.1.2 Words with semantic restrictions.
'implements',
'let',
'private',
'public',
'yield',
'interface',
'package',
'protected',
'static',
// ES6 11.6.2.1 Keywords (including repeats of ES5 to ease comparison with
// documents).
'break',
'do',
'in',
'typeof',
'case',
'else',
'instanceof',
'var',
'catch',
'export',
'new',
'void',
'class',
'extends',
'return',
'while',
'const',
'finally',
'super',
'with',
'continue',
'for',
'switch',
'yield',
'debugger',
'function',
'this',
'default',
'if',
'throw',
'delete',
'import',
'try',
// ES6 11.6.2.1 Words with semantic restrictions.
'yield', 'let', 'static',
// ES6 11.6.2.2 Future Reserved Words.
'enum',
'await',
// ES6 11.6.2.2 / ES6 12.1.1 Words with semantic restrictions.
'implements',
'package',
'protected',
'interface',
'private',
'public',
// Other words to avoid due to non-standard keyword-like behavior.
];
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",
// 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",
// ES6 collections.
"Map", "Set",
];
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 asyncPrefix = r"$async$";
final String staticStateHolder = r'$';
final String getterPrefix = r'get$';
final String lazyGetterPrefix = r'$get$';
final String setterPrefix = r'set$';
final String superPrefix = r'super$';
final String metadataField = '@';
final String callPrefix = 'call';
// Note: We can't shorten 'call*' in the minified namers because the catch-all
// formula `name + "*"` is used by mirrors.
String get callCatchAllName => r'call*';
final String callNameField = r'$callName';
final String stubNameField = r'$stubName';
final String reflectableField = r'$reflectable';
final String reflectionInfoField = r'$reflectionInfo';
final String reflectionNameField = r'$reflectionName';
final String metadataIndexField = r'$metadataIndex';
String get requiredParameterField => r'$requiredArgCount';
String get defaultValuesField => r'$defaultValues';
final String methodsWithOptionalArgumentsField =
r'$methodsWithOptionalArguments';
final String deferredAction = r'$deferredAction';
final String classDescriptorProperty = r'^';
/// The non-minifying namer's [callPrefix] with a dollar after it.
static const String _callPrefixDollar = r'call$';
static final jsAst.Name _literalDollar = new StringBackedName(r'$');
static final jsAst.Name _literalUnderscore = new StringBackedName('_');
static final jsAst.Name literalPlus = new StringBackedName('+');
static final jsAst.Name _literalDynamic = new StringBackedName("dynamic");
jsAst.Name _literalAsyncPrefix;
jsAst.Name _literalGetterPrefix;
jsAst.Name _literalSetterPrefix;
jsAst.Name _literalLazyGetterPrefix;
jsAst.Name _staticsPropertyName;
jsAst.Name get staticsPropertyName =>
_staticsPropertyName ??= new StringBackedName('static');
final String rtiName = r'$ti';
jsAst.Name _rtiFieldJsName;
jsAst.Name get rtiFieldJsName =>
_rtiFieldJsName ??= new StringBackedName(rtiName);
// 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 JClosedWorld _closedWorld;
final CodegenWorldBuilder _codegenWorldBuilder;
RuntimeTypesEncoder _rtiEncoder;
RuntimeTypesEncoder get rtiEncoder {
assert(_rtiEncoder != null,
failedAt(NO_LOCATION_SPANNABLE, "Namer.rtiEncoder has not been set."));
return _rtiEncoder;
}
void set rtiEncoder(RuntimeTypesEncoder value) {
assert(
_rtiEncoder == null,
failedAt(
NO_LOCATION_SPANNABLE, "Namer.rtiEncoder has already been set."));
_rtiEncoder = value;
}
/// 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 NamingScope globalScope = new NamingScope();
final Map<Entity, jsAst.Name> userGlobals = new HashMap<Entity, jsAst.Name>();
final Map<String, jsAst.Name> internalGlobals =
new HashMap<String, jsAst.Name>();
/// Used disambiguated names in the instance namespace, issued by
/// [_disambiguateMember], [_disambiguateInternalMember],
/// [_disambiguateOperator], and [reservePublicMemberName].
final NamingScope instanceScope = new NamingScope();
final Map<String, jsAst.Name> userInstanceMembers =
new HashMap<String, jsAst.Name>();
final Map<MemberEntity, jsAst.Name> internalInstanceMembers =
new HashMap<MemberEntity, jsAst.Name>();
final Map<String, jsAst.Name> userInstanceOperators =
new HashMap<String, jsAst.Name>();
/// Used to disambiguate names for constants in [constantName].
final NamingScope constantScope = new NamingScope();
/// Used to store scopes for instances of [PrivatelyNamedJsEntity]
final Map<Entity, NamingScope> _privateNamingScopes =
new Map<Entity, NamingScope>();
final Map<String, int> popularNameCounters = <String, int>{};
final Map<LibraryEntity, String> libraryLongNames =
new HashMap<LibraryEntity, String>();
final Map<ConstantValue, jsAst.Name> constantNames =
new HashMap<ConstantValue, jsAst.Name>();
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, LibraryEntity> shortPrivateNameOwners =
<String, LibraryEntity>{};
final Map<String, String> suggestedGlobalNames = <String, String>{};
final Map<String, String> suggestedInstanceNames = <String, String>{};
/// Used to store unique keys for library names. Keys are not used as names,
/// nor are they visible in the output. The only serve as an internal
/// key into maps.
final Map<LibraryEntity, String> _libraryKeys =
new HashMap<LibraryEntity, String>();
Namer(this._closedWorld, this._codegenWorldBuilder) {
_literalAsyncPrefix = new StringBackedName(asyncPrefix);
_literalGetterPrefix = new StringBackedName(getterPrefix);
_literalSetterPrefix = new StringBackedName(setterPrefix);
_literalLazyGetterPrefix = new StringBackedName(lazyGetterPrefix);
}
ElementEnvironment get elementEnvironment => _closedWorld.elementEnvironment;
CommonElements get _commonElements => _closedWorld.commonElements;
NativeData get _nativeData => _closedWorld.nativeData;
String get deferredMetadataName => 'deferredMetadata';
String get deferredTypesName => 'deferredTypes';
String get isolateName => 'Isolate';
String get isolatePropertiesName => r'$isolateProperties';
jsAst.Name get noSuchMethodName => invocationName(Selectors.noSuchMethod_);
/**
* 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 => Identifiers.call;
bool get shouldMinify => false;
NamingScope _getPrivateScopeFor(PrivatelyNamedJSEntity entity) {
return _privateNamingScopes.putIfAbsent(
entity.rootOfScope, () => new NamingScope());
}
/// Returns the string that is to be used as the result of a call to
/// [JS_GET_NAME] at [node] with argument [name].
jsAst.Name getNameForJsGetName(Spannable spannable, JsGetName name) {
switch (name) {
case JsGetName.GETTER_PREFIX:
return asName(getterPrefix);
case JsGetName.SETTER_PREFIX:
return asName(setterPrefix);
case JsGetName.CALL_PREFIX:
return asName(callPrefix);
case JsGetName.CALL_PREFIX0:
return asName('${callPrefix}\$0');
case JsGetName.CALL_PREFIX1:
return asName('${callPrefix}\$1');
case JsGetName.CALL_PREFIX2:
return asName('${callPrefix}\$2');
case JsGetName.CALL_PREFIX3:
return asName('${callPrefix}\$3');
case JsGetName.CALL_PREFIX4:
return asName('${callPrefix}\$4');
case JsGetName.CALL_PREFIX5:
return asName('${callPrefix}\$5');
case JsGetName.CALL_CATCH_ALL:
return asName(callCatchAllName);
case JsGetName.REFLECTABLE:
return asName(reflectableField);
case JsGetName.CLASS_DESCRIPTOR_PROPERTY:
return asName(classDescriptorProperty);
case JsGetName.REQUIRED_PARAMETER_PROPERTY:
return asName(requiredParameterField);
case JsGetName.DEFAULT_VALUES_PROPERTY:
return asName(defaultValuesField);
case JsGetName.CALL_NAME_PROPERTY:
return asName(callNameField);
case JsGetName.DEFERRED_ACTION_PROPERTY:
return asName(deferredAction);
case JsGetName.OPERATOR_AS_PREFIX:
return asName(operatorAsPrefix);
case JsGetName.SIGNATURE_NAME:
return asName(operatorSignature);
case JsGetName.RTI_NAME:
return asName(rtiName);
case JsGetName.TYPEDEF_TAG:
return asName(typedefTag);
case JsGetName.FUNCTION_TYPE_TAG:
return asName(functionTypeTag);
case JsGetName.FUNCTION_TYPE_GENERIC_BOUNDS_TAG:
return asName(functionTypeGenericBoundsTag);
case JsGetName.FUNCTION_TYPE_VOID_RETURN_TAG:
return asName(functionTypeVoidReturnTag);
case JsGetName.FUNCTION_TYPE_RETURN_TYPE_TAG:
return asName(functionTypeReturnTypeTag);
case JsGetName.FUNCTION_TYPE_REQUIRED_PARAMETERS_TAG:
return asName(functionTypeRequiredParametersTag);
case JsGetName.FUNCTION_TYPE_OPTIONAL_PARAMETERS_TAG:
return asName(functionTypeOptionalParametersTag);
case JsGetName.FUNCTION_TYPE_NAMED_PARAMETERS_TAG:
return asName(functionTypeNamedParametersTag);
case JsGetName.FUTURE_OR_TAG:
return asName(futureOrTag);
case JsGetName.FUTURE_OR_TYPE_ARGUMENT_TAG:
return asName(futureOrTypeTag);
case JsGetName.IS_INDEXABLE_FIELD_NAME:
return operatorIs(_commonElements.jsIndexingBehaviorInterface);
case JsGetName.NULL_CLASS_TYPE_NAME:
return runtimeTypeName(_commonElements.nullClass);
case JsGetName.OBJECT_CLASS_TYPE_NAME:
return runtimeTypeName(_commonElements.objectClass);
case JsGetName.FUNCTION_CLASS_TYPE_NAME:
return runtimeTypeName(_commonElements.functionClass);
case JsGetName.FUTURE_CLASS_TYPE_NAME:
return runtimeTypeName(_commonElements.futureClass);
default:
throw failedAt(spannable, 'Error: Namer has no name for "$name".');
}
}
/// Return a reference to the given [name].
///
/// This is used to ensure that every use site of a name has a unique node so
/// that we can properly attribute source information.
jsAst.Name _newReference(jsAst.Name name) {
return new _NameReference(name);
}
/// Disambiguated name for [constant].
///
/// Unique within the global-member namespace.
jsAst.Name 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);
jsAst.Name result = constantNames[constant];
if (result == null) {
String longName = constantLongName(constant);
result = getFreshName(constantScope, longName);
constantNames[constant] = result;
}
return _newReference(result);
}
/// Proposed name for [constant].
String constantLongName(ConstantValue constant) {
String longName = constantLongNames[constant];
if (longName == null) {
_constantHasher ??=
new ConstantCanonicalHasher(rtiEncoder, _codegenWorldBuilder);
longName = new ConstantNamingVisitor(
rtiEncoder, _codegenWorldBuilder, _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;
LibraryEntity library = originalName.library;
// The first library asking for a short private name wins.
LibraryEntity 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 and as the library
// name itself might clash.
String libraryName = _proposeNameForLibrary(library);
return "_$libraryName\$$text";
}
}
String _proposeNameForConstructorBody(ConstructorBodyEntity method) {
String name = utils.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.parameterStructure);
return '$name\$${suffix.join(r'$')}';
}
/// Name for a constructor body.
jsAst.Name constructorBodyName(ConstructorBodyEntity ctor) {
return _disambiguateInternalMember(
ctor, () => _proposeNameForConstructorBody(ctor));
}
/// Name for a generator body.
jsAst.Name generatorBodyInstanceMethodName(JGeneratorBody method) {
assert(method.isInstanceMember);
// TODO(sra): Except for methods declared in mixins, we can use a compact
// naming scheme like we do for [ConstructorBodyEntity].
FunctionEntity function = method.function;
return _disambiguateInternalMember(method, () {
String invocationName = operatorNameToIdentifier(function.name);
return '${invocationName}\$body\$${method.enclosingClass.name}';
});
}
/// Annotated name for [method] encoding arity and named parameters.
jsAst.Name instanceMethodName(FunctionEntity method) {
// TODO(johnniwinther): Avoid the use of [ConstructorBodyEntity] and
// [JGeneratorBody]. The codegen model should be explicit about its
// constructor body elements.
if (method is ConstructorBodyEntity) {
return constructorBodyName(method);
}
if (method is JGeneratorBody) {
return generatorBodyInstanceMethodName(method);
}
return invocationName(new Selector.fromElement(method));
}
/// 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.
jsAst.Name deriveCallMethodName(List<String> suffix) {
// TODO(asgerf): Avoid clashes when named parameters contain $ symbols.
return new StringBackedName('$callPrefix\$${suffix.join(r'$')}');
}
/// The suffix list for the pattern:
///
/// $<T>$<N>$namedParam1...$namedParam<M>
///
/// Where <T> is the number of type arguments, <N> is the number of positional
/// arguments and <M> is the number of named arguments.
///
/// If there are no type arguments the `$<T>` is omitted.
///
/// This is used for the annotated names of `call`, and for the proposed name
/// for other instance methods.
static List<String> callSuffixForStructure(CallStructure callStructure) {
List<String> suffixes = [];
if (callStructure.typeArgumentCount > 0) {
suffixes.add('${callStructure.typeArgumentCount}');
}
suffixes.add('${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(ParameterStructure parameterStructure) {
List<String> suffixes = ['${parameterStructure.totalParameters}'];
suffixes.addAll(parameterStructure.namedParameters);
return suffixes;
}
/// Annotated name for the member being invoked by [selector].
jsAst.Name invocationName(Selector selector) {
switch (selector.kind) {
case SelectorKind.GETTER:
jsAst.Name disambiguatedName = _disambiguateMember(selector.memberName);
return deriveGetterName(disambiguatedName);
case SelectorKind.SETTER:
jsAst.Name disambiguatedName = _disambiguateMember(selector.memberName);
return deriveSetterName(disambiguatedName);
case SelectorKind.OPERATOR:
case SelectorKind.INDEX:
String operatorIdentifier = operatorNameToIdentifier(selector.name);
jsAst.Name disambiguatedName =
_disambiguateOperator(operatorIdentifier);
return disambiguatedName; // Operators are not annotated.
case SelectorKind.CALL:
List<String> suffix = callSuffixForStructure(selector.callStructure);
if (selector.name == Identifiers.call) {
// Derive the annotated name for this variant of 'call'.
return deriveCallMethodName(suffix);
}
jsAst.Name disambiguatedName =
_disambiguateMember(selector.memberName, suffix);
return disambiguatedName; // Methods other than call are not annotated.
case SelectorKind.SPECIAL:
return specialSelectorName(selector);
default:
throw failedAt(CURRENT_ELEMENT_SPANNABLE,
'Unexpected selector kind: ${selector.kind}');
}
}
jsAst.Name specialSelectorName(Selector selector) {
assert(selector.kind == SelectorKind.SPECIAL);
if (selector.memberName == Names.genericInstantiation) {
return new StringBackedName('${genericInstantiationPrefix}'
'${selector.callStructure.typeArgumentCount}');
}
throw failedAt(
CURRENT_ELEMENT_SPANNABLE, 'Unexpected special selector: $selector');
}
/**
* Returns the internal name used for an invocation mirror of this selector.
*/
jsAst.Name invocationMirrorInternalName(Selector selector) =>
invocationName(selector);
/**
* Returns the disambiguated name for the given field, used for constructing
* the getter and setter names.
*/
jsAst.Name fieldAccessorName(FieldEntity element) {
return element.isInstanceMember
? _disambiguateMember(element.memberName)
: _disambiguateGlobalMember(element);
}
/**
* Returns name of the JavaScript property used to store a static or instance
* field.
*/
jsAst.Name fieldPropertyName(FieldEntity element) {
return element.isInstanceMember
? instanceFieldPropertyName(element)
: _disambiguateGlobalMember(element);
}
/// Returns a JavaScript property name used to store the member [element] on
/// one of the global objects.
///
/// Should be used together with [globalObjectForMember], which denotes the
/// object on which the returned property name should be used.
jsAst.Name globalPropertyNameForMember(MemberEntity element) =>
_disambiguateGlobalMember(element);
/// Returns a JavaScript property name used to store the class [element] on
/// one of the global objects.
///
/// Should be used together with [globalObjectForClass], which denotes the
/// object on which the returned property name should be used.
jsAst.Name globalPropertyNameForClass(ClassEntity element) =>
_disambiguateGlobalType(element);
/// Returns a JavaScript property name used to store the type (typedef)
/// [element] on one of the global objects.
///
/// Should be used together with [globalObjectForType], which denotes the
/// object on which the returned property name should be used.
jsAst.Name globalPropertyNameForType(Entity element) =>
_disambiguateGlobalType(element);
/**
* Returns the JavaScript property name used to store an instance field.
*/
jsAst.Name instanceFieldPropertyName(FieldEntity element) {
ClassEntity enclosingClass = element.enclosingClass;
if (_nativeData.hasFixedBackendName(element)) {
return new StringBackedName(_nativeData.getFixedBackendName(element));
}
// Some elements, like e.g. instances of BoxFieldElement are special.
// They are created with a unique and safe name for the element model.
// While their name is unique, it is not very readable. So we try to
// preserve the original, proposed 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. So we can directly grab a name.
if (element is JSEntity) {
return _disambiguateInternalMember(
element, () => (element as JSEntity).declaredEntity.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.
if (_closedWorld.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(new Name(element.name, element.library));
}
bool _isShadowingSuperField(FieldEntity element) {
assert(element.isField);
String fieldName = element.name;
bool isPrivate = Name.isPrivateName(fieldName);
LibraryEntity memberLibrary = element.library;
ClassEntity lookupClass =
elementEnvironment.getSuperClass(element.enclosingClass);
while (lookupClass != null) {
MemberEntity foundMember =
elementEnvironment.lookupLocalClassMember(lookupClass, fieldName);
if (foundMember != null) {
if (foundMember.isField) {
if (!isPrivate || memberLibrary == foundMember.library) {
// Private fields can only be shadowed by a field declared in the
// same library.
return true;
}
}
}
lookupClass = elementEnvironment.getSuperClass(lookupClass);
}
return false;
}
/// True if [class_] is a non-native class that inherits from a native class.
bool _isUserClassExtendingNative(ClassEntity class_) {
return !_nativeData.isNativeClass(class_) &&
_nativeData.isNativeOrExtendsNative(class_);
}
/// Annotated name for the setter of [element].
jsAst.Name setterForMember(MemberEntity element) {
// We dynamically create setters from the field-name. The setter name must
// therefore be derived from the instance field-name.
jsAst.Name name = _disambiguateMember(element.memberName);
return deriveSetterName(name);
}
/// Annotated name for the setter of any member with [disambiguatedName].
jsAst.Name deriveSetterName(jsAst.Name disambiguatedName) {
// We dynamically create setters from the field-name. The setter name must
// therefore be derived from the instance field-name.
return new SetterName(_literalSetterPrefix, disambiguatedName);
}
/// Annotated name for the setter of any member with [disambiguatedName].
jsAst.Name deriveGetterName(jsAst.Name disambiguatedName) {
// We dynamically create getters from the field-name. The getter name must
// therefore be derived from the instance field-name.
return new GetterName(_literalGetterPrefix, disambiguatedName);
}
/// Annotated name for the getter of [element].
jsAst.Name getterForElement(MemberEntity element) {
// We dynamically create getters from the field-name. The getter name must
// therefore be derived from the instance field-name.
jsAst.Name name = _disambiguateMember(element.memberName);
return deriveGetterName(name);
}
/// Property name for the getter of an instance member with [originalName].
jsAst.Name getterForMember(Name originalName) {
jsAst.Name disambiguatedName = _disambiguateMember(originalName);
return deriveGetterName(disambiguatedName);
}
/// Disambiguated name for a compiler-owned global variable.
///
/// The resulting name is unique within the global-member namespace.
jsAst.Name _disambiguateInternalGlobal(String name) {
jsAst.Name newName = internalGlobals[name];
if (newName == null) {
newName = getFreshName(globalScope, name);
internalGlobals[name] = newName;
}
return _newReference(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.
jsAst.Name internalGlobal(String name) {
assert(!name.contains(r'$'));
return _disambiguateInternalGlobal(name);
}
/// Generates a unique key for [library].
///
/// Keys are meant to be used in maps and should not be visible in the output.
String _generateLibraryKey(LibraryEntity library) {
return _libraryKeys.putIfAbsent(library, () {
String keyBase = library.name;
int counter = 0;
String key = keyBase;
while (_libraryKeys.values.contains(key)) {
key = "$keyBase${counter++}";
}
return key;
});
}
jsAst.Name _disambiguateGlobalMember(MemberEntity element) {
return _disambiguateGlobal<MemberEntity>(element, _proposeNameForMember);
}
jsAst.Name _disambiguateGlobalType(Entity element) {
return _disambiguateGlobal(element, _proposeNameForType);
}
/// Returns the disambiguated name for a top-level or static element.
///
/// The resulting name is unique within the global-member namespace.
jsAst.Name _disambiguateGlobal<T extends Entity>(
T element, String proposeName(T element)) {
// TODO(asgerf): We can reuse more short names if we disambiguate with
// a separate namespace for each of the global holder objects.
jsAst.Name newName = userGlobals[element];
if (newName == null) {
String proposedName = proposeName(element);
newName = getFreshName(globalScope, proposedName);
userGlobals[element] = newName;
}
return _newReference(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 distinguish 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.
jsAst.Name _disambiguateMember(Name originalName,
[List<String> suffixes = const []]) {
// Build a string encoding the library name, if the name is private.
String libraryKey =
originalName.isPrivate ? _generateLibraryKey(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('@')}';
jsAst.Name 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(instanceScope, proposedName,
sanitizeForAnnotations: true);
userInstanceMembers[key] = newName;
}
return _newReference(newName);
}
/// Returns the disambiguated name for the instance member identified by
/// [key].
///
/// When a name for an element is requested by key, it may not be requested
/// by element at the same time, as two different names would be returned.
///
/// If key has not yet been registered, [proposeName] is used to generate
/// a name proposal for the given key.
///
/// [key] must not clash with valid instance names. This is typically
/// achieved by using at least one character in [key] that is not valid in
/// identifiers, for example the @ symbol.
jsAst.Name _disambiguateMemberByKey(String key, String proposeName()) {
jsAst.Name newName = userInstanceMembers[key];
if (newName == null) {
String name = proposeName();
newName = getFreshName(instanceScope, name, sanitizeForAnnotations: true);
userInstanceMembers[key] = newName;
}
return _newReference(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(!instanceScope.isUsed(disambiguatedName));
userInstanceMembers[key] = new StringBackedName(disambiguatedName);
instanceScope.registerUse(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.
jsAst.Name _disambiguateInternalMember(
MemberEntity element, String proposeName()) {
jsAst.Name newName = internalInstanceMembers[element];
if (newName == null) {
String name = proposeName();
if (element is PrivatelyNamedJSEntity) {
NamingScope scope = _getPrivateScopeFor(element);
newName = getFreshName(scope, name,
sanitizeForAnnotations: true, sanitizeForNatives: false);
internalInstanceMembers[element] = newName;
} else {
bool mayClashNative =
_isUserClassExtendingNative(element.enclosingClass);
newName = getFreshName(instanceScope, name,
sanitizeForAnnotations: true, sanitizeForNatives: mayClashNative);
internalInstanceMembers[element] = newName;
}
}
return _newReference(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.
jsAst.Name _disambiguateOperator(String operatorIdentifier) {
jsAst.Name newName = userInstanceOperators[operatorIdentifier];
if (newName == null) {
newName = getFreshName(instanceScope, operatorIdentifier);
userInstanceOperators[operatorIdentifier] = newName;
}
return _newReference(newName);
}
String _generateFreshStringForName(String proposedName, NamingScope scope,
{bool sanitizeForAnnotations: false, bool sanitizeForNatives: false}) {
if (sanitizeForAnnotations) {
proposedName = _sanitizeForAnnotations(proposedName);
}
if (sanitizeForNatives) {
proposedName = _sanitizeForNatives(proposedName);
}
proposedName = _sanitizeForKeywords(proposedName);
String candidate;
if (scope.isUnused(proposedName)) {
candidate = proposedName;
} else {
int counter = popularNameCounters[proposedName];
int i = (counter == null) ? 0 : counter;
while (scope.isUsed("$proposedName$i")) {
i++;
}
popularNameCounters[proposedName] = i + 1;
candidate = "$proposedName$i";
}
scope.registerUse(candidate);
return candidate;
}
/// 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.
jsAst.Name getFreshName(NamingScope scope, String proposedName,
{bool sanitizeForAnnotations: false, bool sanitizeForNatives: false}) {
String candidate = _generateFreshStringForName(proposedName, scope,
sanitizeForAnnotations: sanitizeForAnnotations,
sanitizeForNatives: sanitizeForNatives);
return new StringBackedName(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 typedef or class [element].
/// The returned id is guaranteed to be a valid JavaScript identifier.
String _proposeNameForType(Entity element) {
return element.name.replaceAll(_nonIdentifierRE, '_');
}
static RegExp _nonIdentifierRE = new RegExp(r'[^A-Za-z0-9_$]');
/// Returns a proposed name for the given top-level or static member
/// [element]. The returned id is guaranteed to be a valid JavaScript
/// identifier.
String _proposeNameForMember(MemberEntity element) {
if (element.isConstructor) {
return _proposeNameForConstructor(element);
} else if (element is JGeneratorBody) {
return _proposeNameForMember(element.function) + r'$body';
} else if (element.enclosingClass != null) {
ClassEntity enclosingClass = element.enclosingClass;
return '${enclosingClass.name}_${element.name}';
}
return element.name.replaceAll('+', '_');
}
String _proposeNameForConstructor(ConstructorEntity element) {
String className = element.enclosingClass.name;
if (element.isGenerativeConstructor) {
return '${className}\$${element.name}';
} else {
// TODO(johnniwinther): Change factory name encoding as to not include
// the class-name twice.
return '${className}_${utils.reconstructConstructorName(element)}';
}
}
/**
* Returns a proposed name for the given [LibraryElement].
* The returned id is guaranteed to be a valid JavaScript identifier.
*/
// TODO(sra): Pre-process libraries to assign [libraryLongNames] in a way that
// is independent of the order of calls to namer.
String _proposeNameForLibrary(LibraryEntity library) {
String name = libraryLongNames[library];
if (name != null) return name;
// Use the 'file' name, e.g. "package:expect/expect.dart" -> "expect"
name = library.canonicalUri.path;
name = name.substring(name.lastIndexOf('/') + 1);
if (name.contains('.')) {
// Drop file extension.
name = name.substring(0, name.lastIndexOf('.'));
}
// The filename based name can contain all kinds of nasty characters. Make
// sure it is an identifier.
if (!IDENTIFIER.hasMatch(name)) {
String replacer(Match match) {
String s = match[0];
if (s == '.') return '_';
return s.codeUnitAt(0).toRadixString(16);
}
name = name.replaceAllMapped(NON_IDENTIFIER_CHAR, replacer);
if (!IDENTIFIER.hasMatch(name)) {
// e.g. starts with digit.
name = 'lib_$name';
}
}
// Names constructed based on a libary name will be further disambiguated.
// However, as names from the same libary should have the same library
// name part, we disambiguate the library name here.
String disambiguated = name;
for (int c = 0; libraryLongNames.containsValue(disambiguated); c++) {
disambiguated = "$name$c";
}
libraryLongNames[library] = disambiguated;
return disambiguated;
}
String suffixForGetInterceptor(Iterable<ClassEntity> classes) {
String abbreviate(ClassEntity cls) {
if (cls == _commonElements.objectClass) return "o";
if (cls == _commonElements.jsStringClass) return "s";
if (cls == _commonElements.jsArrayClass) return "a";
if (cls == _commonElements.jsDoubleClass) return "d";
if (cls == _commonElements.jsIntClass) return "i";
if (cls == _commonElements.jsNumberClass) return "n";
if (cls == _commonElements.jsNullClass) return "u";
if (cls == _commonElements.jsBoolClass) return "b";
if (cls == _commonElements.jsInterceptorClass) return "I";
return cls.name;
}
List<String> names = classes
.where((cls) => !_nativeData.isNativeOrExtendsNative(cls))
.map(abbreviate)
.toList();
// There is one dispatch mechanism for all native classes.
if (classes.any((cls) => _nativeData.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.
jsAst.Name nameForGetInterceptor(Iterable<ClassEntity> classes) {
FunctionEntity getInterceptor = _commonElements.getInterceptorMethod;
if (classes.contains(_commonElements.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 _disambiguateGlobalMember(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.
jsAst.Name nameForGetOneShotInterceptor(
Selector selector, Iterable<ClassEntity> 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.
jsAst.Name root = invocationName(selector);
if (classes.contains(_commonElements.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.
// 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".
return new CompoundName([root, _literalDollar]);
} else {
String suffix = suffixForGetInterceptor(classes);
return new CompoundName(
[root, _literalDollar, new StringBackedName(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.
jsAst.Name runtimeTypeName(Entity element) {
if (element == null) return _literalDynamic;
// 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 _disambiguateGlobalType(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.
jsAst.Name className(ClassEntity class_) => _disambiguateGlobalType(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()
/// }
///
jsAst.Name aliasedSuperMemberPropertyName(MemberEntity member) {
assert(!member.isField); // Fields do not need super aliases.
return _disambiguateInternalMember(member, () {
String invocationName = operatorNameToIdentifier(member.name);
return "super\$${member.enclosingClass.name}\$$invocationName";
});
}
/// 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.
jsAst.Name methodPropertyName(FunctionEntity method) {
return method.isInstanceMember
? instanceMethodName(method)
: globalPropertyNameForMember(method);
}
/// Returns true if [element] is stored in the static state holder
/// ([staticStateHolder]). We intend to store only mutable static state
/// there, whereas constants are stored in 'C'. Functions, accessors,
/// classes, etc. are stored in one of the other objects in
/// [reservedGlobalObjectNames].
bool _isPropertyOfStaticStateHolder(MemberEntity element) {
// TODO(ahe): Make sure this method's documentation is always true and
// remove the word "intend".
return element.isField;
}
/// Returns [staticStateHolder] or one of [reservedGlobalObjectNames].
String globalObjectForMember(MemberEntity element) {
if (_isPropertyOfStaticStateHolder(element)) return staticStateHolder;
return globalObjectForLibrary(element.library);
}
String globalObjectForClass(ClassEntity element) {
return globalObjectForLibrary(element.library);
}
String globalObjectForType(Entity element) {
if (element is TypedefEntity) {
return globalObjectForLibrary(element.library);
}
return globalObjectForClass(element);
}
/// Returns the [reservedGlobalObjectNames] for [library].
String globalObjectForLibrary(LibraryEntity library) {
if (library == _commonElements.interceptorsLibrary) return 'J';
Uri uri = library.canonicalUri;
if (uri.scheme == 'dart') {
if (uri.path == 'html') return 'W';
if (uri.path.startsWith('_')) return 'H';
return 'P';
}
return userGlobalObjects[library.name.hashCode % userGlobalObjects.length];
}
jsAst.Name deriveLazyInitializerName(jsAst.Name name) {
// These are not real dart getters, so do not use GetterName;
return new CompoundName([_literalLazyGetterPrefix, name]);
}
jsAst.Name lazyInitializerName(FieldEntity element) {
assert(element.isTopLevel || element.isStatic);
jsAst.Name name = _disambiguateGlobalMember(element);
// These are not real dart getters, so do not use GetterName;
return deriveLazyInitializerName(name);
}
jsAst.Name staticClosureName(FunctionEntity element) {
assert(element.isTopLevel || element.isStatic);
String enclosing =
element.enclosingClass == null ? "" : element.enclosingClass.name;
String library = _proposeNameForLibrary(element.library);
return _disambiguateInternalGlobal(
"${library}_${enclosing}_${element.name}\$closure");
}
// This name is used as part of the name of a TypeConstant
String uniqueNameForTypeConstantElement(
LibraryEntity library, Entity element) {
// TODO(sra): If we replace the period with an identifier character,
// TypeConstants will have better names in unminified code.
String libraryName = _proposeNameForLibrary(library);
return "${libraryName}.${element.name}";
}
String globalObjectForConstant(ConstantValue constant) => 'C';
String get operatorIsPrefix => r'$is';
String get operatorAsPrefix => r'$as';
String get operatorSignature => r'$signature';
String get genericInstantiationPrefix => r'$instantiate';
String get typedefTag => r'typedef';
String get functionTypeTag => r'func';
String get functionTypeVoidReturnTag => r'v';
String get functionTypeReturnTypeTag => r'ret';
String get functionTypeRequiredParametersTag => r'args';
String get functionTypeOptionalParametersTag => r'opt';
String get functionTypeNamedParametersTag => r'named';
String get functionTypeGenericBoundsTag => r'bounds';
String get futureOrTag => r'futureOr';
String get futureOrTypeTag => r'type';
// The name of the variable used to offset function signatures in deferred
// parts with the fast-startup emitter.
String get typesOffsetName => r'typesOffset';
Map<FunctionType, jsAst.Name> functionTypeNameMap =
new HashMap<FunctionType, jsAst.Name>();
FunctionTypeNamer _functionTypeNamer;
jsAst.Name getFunctionTypeName(FunctionType functionType) {
return functionTypeNameMap.putIfAbsent(functionType, () {
_functionTypeNamer ??= new FunctionTypeNamer(rtiEncoder);
String proposedName = _functionTypeNamer.computeName(functionType);
return getFreshName(instanceScope, proposedName);
});
}
jsAst.Name operatorIsType(DartType type) {
if (type.isFunctionType) {
// TODO(erikcorry): Reduce from $isx to ix when we are minifying.
return new CompoundName([
new StringBackedName(operatorIsPrefix),
_literalUnderscore,
getFunctionTypeName(type)
]);
}
InterfaceType interfaceType = type;
return operatorIs(interfaceType.element);
}
jsAst.Name operatorIs(ClassEntity element) {
// TODO(erikcorry): Reduce from $isx to ix when we are minifying.
return new CompoundName(
[new StringBackedName(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;
}
jsAst.Name substitutionName(ClassEntity element) {
return new CompoundName(
[new StringBackedName(operatorAsPrefix), runtimeTypeName(element)]);
}
/// Translates a [String] into the corresponding [Name] data structure as
/// used by the namer.
///
/// If [name] is a setter or getter name, the corresponding [GetterName] or
/// [SetterName] data structure is used.
jsAst.Name asName(String name) {
if (name.startsWith(getterPrefix) && name.length > getterPrefix.length) {
return new GetterName(_literalGetterPrefix,
new StringBackedName(name.substring(getterPrefix.length)));
}
if (name.startsWith(setterPrefix) && name.length > setterPrefix.length) {
return new GetterName(_literalSetterPrefix,
new StringBackedName(name.substring(setterPrefix.length)));
}
return new StringBackedName(name);
}
/// 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) {
name = name.replaceAll('#', '_');
if (jsVariableReserved.contains(name) || name.startsWith(r'$')) {
return '\$$name';
}
return name;
}
/// Returns a safe variable name for use in async rewriting.
///
/// Has the same property as [safeVariableName] but does not clash with
/// names returned from there.
/// Additionally, when used as a prefix to a variable name, the result
/// will be safe to use, as well.
String safeVariablePrefixForAsyncRewrite(String name) {
return "$asyncPrefix$name";
}
jsAst.Name deriveAsyncBodyName(jsAst.Name original) {
return new _AsyncName(_literalAsyncPrefix, original);
}
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;
}
}
}
/**
* 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 RuntimeTypesEncoder rtiEncoder;
final CodegenWorldBuilder codegenWorldBuilder;
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.rtiEncoder, this.codegenWorldBuilder, 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 visitInstantiation(InstantiationConstantValue constant, [_]) {
_visit(constant.function);
}
@override
void visitNull(NullConstantValue constant, [_]) {
add('null');
}
@override
void visitNonConstant(NonConstantValue constant, [_]) {
add('null');
}
@override
void visitInt(IntConstantValue constant, [_]) {
// No `addRoot` since IntConstants are always inlined.
if (constant.intValue < BigInt.zero) {
add('m${-constant.intValue}');
} else {
add('${constant.intValue}');
}
}
@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.stringValue);
}
@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(covariant 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);
// Recognize enum constants and only include the index.
final Map<FieldEntity, ConstantValue> fieldMap = constant.fields;
int size = fieldMap.length;
if (size == 1 || size == 2) {
FieldEntity indexField;
for (FieldEntity field in fieldMap.keys) {
String name = field.name;
if (name == 'index') {
indexField = field;
} else if (name == '_name') {
// Ignore _name field.
} else {
indexField = null;
break;
}
}
if (indexField != null) {
_visit(constant.fields[indexField]);
return;
}
}
// TODO(johnniwinther): This should be accessed from a codegen closed world.
codegenWorldBuilder.forEachInstanceField(constant.type.element,
(_, FieldEntity field) {
if (failed) return;
_visit(constant.fields[field]);
});
}
@override
void visitType(TypeConstantValue constant, [_]) {
// Generates something like 'Type_String_k8F', using the simple name of the
// type and a hash to disambiguate the same name in different libraries.
addRoot('Type');
DartType type = constant.representedType;
String name;
if (type is InterfaceType) {
name = type.element.name;
} else if (type is TypedefType) {
name = type.element.name;
}
if (name == null) {
// e.g. DartType 'dynamic' has no element.
name = rtiEncoder.getTypeRepresentationForTypeConstant(type);
}
addIdentifier(name);
add(getHashTag(constant, 3));
}
@override
void visitInterceptor(InterceptorConstantValue constant, [_]) {
// The class name for mixin applications contain '+' signs (issue 28196).
addRoot(constant.cls.name.replaceAll('+', '_'));
add('methods');
}
@override
void visitSynthetic(SyntheticConstantValue constant, [_]) {
switch (constant.valueKind) {
case SyntheticConstantKind.DUMMY_INTERCEPTOR:
add('dummy_receiver');
break;
case SyntheticConstantKind.TYPEVARIABLE_REFERENCE:
// Omit. These are opaque deferred indexes with nothing helpful to add.
break;
case SyntheticConstantKind.NAME:
add('name');
break;
default:
failedAt(
CURRENT_ELEMENT_SPANNABLE, "Unexpected SyntheticConstantValue");
}
}
@override
void visitDeferred(DeferredConstantValue constant, [_]) {
addRoot('Deferred');
}
@override
void visitDeferredGlobal(DeferredGlobalConstantValue 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 RuntimeTypesEncoder rtiEncoder;
final CodegenWorldBuilder codegenWorldBuilder;
final Map<ConstantValue, int> hashes = new Map<ConstantValue, int>();
ConstantCanonicalHasher(this.rtiEncoder, this.codegenWorldBuilder);
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 visitNonConstant(NonConstantValue 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 visitInstantiation(InstantiationConstantValue constant, [_]) {
return _visit(constant.function);
}
@override
int visitInt(IntConstantValue constant, [_]) {
BigInt value = constant.intValue;
if (value.toSigned(32) == value) {
return value.toUnsigned(32).toInt() & _MASK;
}
return _hashDouble(value.toDouble());
}
@override
int visitDouble(DoubleConstantValue constant, [_]) {
return _hashDouble(constant.doubleValue);
}
@override
int visitString(StringConstantValue constant, [_]) {
return _hashString(2, constant.stringValue);
}
@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);
codegenWorldBuilder.forEachInstanceField(constant.type.element,
(_, FieldEntity field) {
hash = _combine(hash, _visit(constant.fields[field]));
});
return hash;
}
@override
int visitType(TypeConstantValue constant, [_]) {
DartType type = constant.representedType;
// This name includes the library name and type parameters.
String name = rtiEncoder.getTypeRepresentationForTypeConstant(type);
return _hashString(4, name);
}
@override
int visitInterceptor(InterceptorConstantValue constant, [_]) {
String typeName = constant.cls.name;
return _hashString(5, typeName);
}
@override
int visitSynthetic(SyntheticConstantValue constant, [_]) {
switch (constant.valueKind) {
case SyntheticConstantKind.TYPEVARIABLE_REFERENCE:
// These contain a deferred opaque index into metadata. There is nothing
// we can access that is stable between compiles. Luckily, since they
// resolve to integer indexes, they're always part of a larger constant.
return 0;
default:
throw failedAt(
NO_LOCATION_SPANNABLE,
'SyntheticConstantValue should never be named and '
'never be subconstant');
}
}
@override
int visitDeferred(DeferredConstantValue constant, [_]) {
// TODO(sra): Investigate that the use of hashCode here is probably a source
// of instability.
int hash = constant.import.hashCode;
return _combine(hash, _visit(constant.referenced));
}
@override
int visitDeferredGlobal(DeferredGlobalConstantValue constant, [_]) {
int hash = constant.unit.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 BaseDartTypeVisitor {
final RuntimeTypesEncoder rtiEncoder;
StringBuffer sb;
FunctionTypeNamer(this.rtiEncoder);
String computeName(DartType type) {
sb = new StringBuffer();
visit(type);
return sb.toString();
}
visit(DartType type, [_]) {
type.accept(this, null);
}
visitType(DartType type, _) {}
visitInterfaceType(InterfaceType type, _) {
sb.write(type.element.name);
}
visitTypedefType(TypedefType type, _) {
sb.write(type.element.name);
}
visitTypeVariableType(TypeVariableType type, _) {
sb.write(type.element.name);
}
visitFunctionType(FunctionType type, _) {
if (rtiEncoder.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;
}
}
}
}
class NamingScope {
/// 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> _suggestedNames = new Map<String, String>();
final Set<String> _usedNames = new Set<String>();
bool isUsed(String name) => _usedNames.contains(name);
bool isUnused(String name) => !_usedNames.contains(name);
bool registerUse(String name) => _usedNames.add(name);
String suggestName(String original) => _suggestedNames[original];
void addSuggestion(String original, String suggestion) {
assert(!_suggestedNames.containsKey(original));
_suggestedNames[original] = suggestion;
}
bool hasSuggestion(String original) => _suggestedNames.containsKey(original);
bool isSuggestion(String candidate) {
return _suggestedNames.containsValue(candidate);
}
}