blob: 5ebc469b3f0ef8df533c838fc98dc96162ee8245 [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/api_unstable/dart2js.dart'
show $0, $9, $A, $Z, $_, $a, $g, $s, $z;
import 'package:js_runtime/shared/embedded_names.dart' show JsGetName;
import '../closure.dart';
import '../common.dart';
import '../common/codegen.dart';
import '../common/names.dart' show Identifiers, Names, Selectors;
import '../common_elements.dart' show JElementEnvironment;
import '../constants/constant_system.dart' as constant_system;
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_backend/field_analysis.dart';
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 '../util/util.dart';
import '../world.dart' show JClosedWorld;
import 'native_data.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 extends ModularNamer {
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", "Symbol", "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;
}
final String lazyGetterPrefix = r'$get$';
final String superPrefix = r'super$';
final String metadataField = '@';
final String stubNameField = r'$stubName';
final String reflectionInfoField = r'$reflectionInfo';
final String reflectionNameField = r'$reflectionName';
final String metadataIndexField = r'$metadataIndex';
final String methodsWithOptionalArgumentsField =
r'$methodsWithOptionalArguments';
@override
final FixedNames fixedNames;
/// 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 _literalGetterPrefix;
jsAst.Name _literalSetterPrefix;
jsAst.Name _staticsPropertyName;
jsAst.Name get staticsPropertyName =>
_staticsPropertyName ??= new StringBackedName('static');
jsAst.Name _rtiFieldJsName;
@override
jsAst.Name get rtiFieldJsName =>
_rtiFieldJsName ??= new StringBackedName(fixedNames.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;
/// 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 = NamingScope();
final Map<Entity, jsAst.Name> userGlobals = {};
// [userGlobalsSecondName] is used when an entity has a second name, e.g. a
// lazily initialized static variable has a location and a getter.
final Map<Entity, jsAst.Name> userGlobalsSecondName = {};
final Map<String, jsAst.Name> internalGlobals = {};
_registerName(
Map<String, String> map, jsAst.Name jsName, String originalName) {
// Non-finalized names are not present in the output program
if (jsName is TokenName && !jsName.isFinalized) return;
map[jsName.name] = originalName;
var getterName = userGetters[jsName];
if (getterName != null) map[getterName.name] = originalName;
var setterName = userSetters[jsName];
if (setterName != null) map[setterName.name] = originalName;
}
Map<String, String> createMinifiedGlobalNameMap() {
var map = <String, String>{};
userGlobals.forEach((entity, jsName) {
_registerName(map, jsName, entity.name);
});
userGlobalsSecondName.forEach((entity, jsName) {
_registerName(map, jsName, entity.name);
});
internalGlobals.forEach((name, jsName) {
_registerName(map, jsName, name);
});
return map;
}
/// Used disambiguated names in the instance namespace, issued by
/// [_disambiguateMember], [_disambiguateInternalMember],
/// [_disambiguateOperator], and [reservePublicMemberName].
final NamingScope instanceScope = NamingScope();
final Map<String, jsAst.Name> userInstanceMembers = HashMap();
final Map<String, String> userInstanceMembersOriginalName = HashMap();
final Map<MemberEntity, jsAst.Name> internalInstanceMembers = HashMap();
final Map<String, jsAst.Name> userInstanceOperators = HashMap();
final Map<jsAst.Name, jsAst.Name> userGetters = HashMap();
final Map<jsAst.Name, jsAst.Name> userSetters = HashMap();
final Map<TypeVariableEntity, jsAst.Name> _typeVariableNames = {};
Map<String, String> createMinifiedInstanceNameMap() {
var map = <String, String>{};
internalInstanceMembers.forEach((entity, jsName) {
_registerName(map, jsName, entity.name);
});
userInstanceMembers.forEach((name, jsName) {
_registerName(map, jsName, userInstanceMembersOriginalName[name] ?? name);
});
// TODO(sigmund): reverse the operator names back to the original Dart
// names.
userInstanceOperators.forEach((name, jsName) {
_registerName(map, jsName, name);
});
return map;
}
/// 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 = {};
final Map<String, int> popularNameCounters = {};
final Map<LibraryEntity, String> libraryLongNames = HashMap();
final Map<ConstantValue, jsAst.Name> _constantNames = HashMap();
final Map<ConstantValue, String> _constantLongNames = {};
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 = {};
/// 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 = HashMap();
Namer(this._closedWorld, this.fixedNames) {
_literalGetterPrefix = new StringBackedName(fixedNames.getterPrefix);
_literalSetterPrefix = new StringBackedName(fixedNames.setterPrefix);
}
JElementEnvironment get _elementEnvironment =>
_closedWorld.elementEnvironment;
@override
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());
}
/// 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(this, _closedWorld);
longName = new ConstantNamingVisitor(this, _closedWorld, _constantHasher)
.getName(constant);
_constantLongNames[constant] = longName;
}
return longName;
}
/// 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;
text = text.replaceAll(_nonIdentifierRE, '_');
// 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);
// TODO(sra): If the generator is for a closure's 'call' method, we don't
// need to incorporate the enclosing class.
String className =
method.enclosingClass.name.replaceAll(_nonIdentifierRE, '_');
return '${invocationName}\$body\$${className}';
});
}
@override
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(
'${fixedNames.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;
}
@override
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);
}
@override
jsAst.Name globalPropertyNameForMember(MemberEntity element) =>
_disambiguateGlobalMember(element);
@override
jsAst.Name globalPropertyNameForClass(ClassEntity element) =>
_disambiguateGlobalType(element);
@override
jsAst.Name globalPropertyNameForType(Entity element) =>
_disambiguateGlobalType(element);
@override
jsAst.Name globalNameForInterfaceTypeVariable(TypeVariableEntity element) {
return _typeVariableNames[element] ??=
_globalNameForInterfaceTypeVariable(element);
}
jsAst.Name _globalNameForInterfaceTypeVariable(TypeVariableEntity element) {
// Construct a name from the class name and type variable,
// e.g. "ListMixin.E". The class name is unique which ensures the type
// variable name is unique.
//
// TODO(sra): Better minified naming. Type variable names are used in type
// recipes and must contain a period ('.'). They can be frequency-assigned
// independently of the class name, e.g. '.a', '.2', 'a.', etc.
String name = element.name;
if (name.length > 1) name = '${element.index}'; // Avoid long names (rare).
return CompoundName([
globalPropertyNameForClass(element.typeDeclaration),
StringBackedName('.$name')
]);
}
@override
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)
.declaredName
.replaceAll(_nonIdentifierRE, '_'));
}
// 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}'
.replaceAll(_nonIdentifierRE, '_');
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 userSetters[disambiguatedName] ??=
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 userGetters[disambiguatedName] ??=
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, userGlobals);
}
jsAst.Name _disambiguateGlobalType(Entity element) {
return _disambiguateGlobal(element, _proposeNameForType, userGlobals);
}
/// 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), Map<Entity, jsAst.Name> globals) {
// 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 = globals[element];
if (newName == null) {
String proposedName = proposeName(element);
newName = getFreshName(globalScope, proposedName);
globals[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;
userInstanceMembersOriginalName[key] = '$originalName';
}
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;
// TODO(sigmund): consider plumbing the original name instead.
userInstanceMembersOriginalName[key] = name;
}
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);
userInstanceMembersOriginalName[key] = originalName;
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(fixedNames.getterPrefix) ||
name.startsWith(fixedNames.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}'
.replaceAll(_nonIdentifierRE, '_');
}
return element.name.replaceAll(_nonIdentifierRE, '_');
}
String _proposeNameForLazyStaticGetter(MemberEntity element) {
return r'$get$' + _proposeNameForMember(element);
}
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 _getSuffixForInterceptedClasses(Iterable<ClassEntity> classes) {
if (classes.isEmpty) {
// TODO(johnniwinther,sra): If [classes] is empty it should either have
// its own suffix (like here), or always be equated with the set of
// classes that contain `Interceptor`. For the latter to work we need to
// update `OneShotInterceptorData.registerSpecializedGetInterceptor`,
// since it currently would otherwise potentially overwrite the all
// intercepted classes case with the empty case.
return 'z';
} else 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 '';
} else {
return suffixForGetInterceptor(_commonElements, _nativeData, classes);
}
}
@override
jsAst.Name nameForGetInterceptor(Iterable<ClassEntity> classes) {
// If the base Interceptor class is in the set of intercepted classes, we
// need to go through the generic getInterceptor method (any subclass of the
// base Interceptor could match), which is encoded as an empty suffix.
String suffix = _getSuffixForInterceptedClasses(classes);
return _disambiguateInternalGlobal('getInterceptor\$$suffix');
}
@override
jsAst.Name nameForOneShotInterceptor(
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);
String suffix = _getSuffixForInterceptedClasses(classes);
return new CompoundName(
[root, _literalDollar, new StringBackedName(suffix)]);
}
@override
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);
}
@override
jsAst.Name className(ClassEntity class_) => _disambiguateGlobalType(class_);
@override
jsAst.Name aliasedSuperMemberPropertyName(MemberEntity member) {
assert(!member.isField); // Fields do not need super aliases.
return _disambiguateInternalMember(member, () {
String className = member.enclosingClass.name.replaceAll('&', '_');
String invocationName = operatorNameToIdentifier(member.name);
return "super\$${className}\$$invocationName";
});
}
@override
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);
}
@override
jsAst.VariableUse readGlobalObjectForMember(MemberEntity element) {
if (_isPropertyOfStaticStateHolder(element)) {
return new jsAst.VariableUse(staticStateHolder);
}
return readGlobalObjectForLibrary(element.library);
}
String globalObjectForClass(ClassEntity element) {
return globalObjectForLibrary(element.library);
}
@override
jsAst.VariableUse readGlobalObjectForClass(ClassEntity element) {
return readGlobalObjectForLibrary(element.library);
}
String globalObjectForType(Entity element) {
return globalObjectForClass(element);
}
@override
jsAst.VariableUse readGlobalObjectForType(Entity element) {
return readGlobalObjectForClass(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];
}
@override
jsAst.VariableUse readGlobalObjectForLibrary(LibraryEntity library) {
return new jsAst.VariableUse(globalObjectForLibrary(library));
}
@override
jsAst.Name lazyInitializerName(FieldEntity element) {
assert(element.isTopLevel || element.isStatic);
jsAst.Name name = _disambiguateGlobal<MemberEntity>(
element, _proposeNameForLazyStaticGetter, userGlobalsSecondName);
return name;
}
@override
jsAst.Name staticClosureName(FunctionEntity element) {
assert(element.isTopLevel || element.isStatic);
String enclosing =
element.enclosingClass == null ? "" : element.enclosingClass.name;
String library = _proposeNameForLibrary(element.library);
String name = element.name.replaceAll(_nonIdentifierRE, '_');
return _disambiguateInternalGlobal(
"${library}_${enclosing}_${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 genericInstantiationPrefix => r'$instantiate';
// 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 = HashMap();
FunctionTypeNamer _functionTypeNamer;
jsAst.Name getFunctionTypeName(FunctionType functionType) {
return functionTypeNameMap.putIfAbsent(functionType, () {
_functionTypeNamer ??= new FunctionTypeNamer();
String proposedName = _functionTypeNamer.computeName(functionType);
return getFreshName(instanceScope, proposedName);
});
}
@override
jsAst.Name operatorIsType(DartType type) {
if (type is FunctionType) {
// TODO(erikcorry): Reduce from $isx to ix when we are minifying.
return new CompoundName([
new StringBackedName(fixedNames.operatorIsPrefix),
_literalUnderscore,
getFunctionTypeName(type)
]);
}
InterfaceType interfaceType = type;
return operatorIs(interfaceType.element);
}
@override
jsAst.Name operatorIs(ClassEntity element) {
// TODO(erikcorry): Reduce from $isx to ix when we are minifying.
return new CompoundName([
new StringBackedName(fixedNames.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;
}
@override
jsAst.Name substitutionName(ClassEntity element) {
return new CompoundName([
new StringBackedName(fixedNames.operatorAsPrefix),
runtimeTypeName(element)
]);
}
@override
jsAst.Name asName(String name) {
if (name.startsWith(fixedNames.getterPrefix) &&
name.length > fixedNames.getterPrefix.length) {
return new GetterName(_literalGetterPrefix,
new StringBackedName(name.substring(fixedNames.getterPrefix.length)));
}
if (name.startsWith(fixedNames.setterPrefix) &&
name.length > fixedNames.setterPrefix.length) {
return new GetterName(_literalSetterPrefix,
new StringBackedName(name.substring(fixedNames.setterPrefix.length)));
}
return new StringBackedName(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'$shru';
} 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 getTypeRepresentationForTypeConstant(DartType type) {
type = type.withoutNullability;
if (type is DynamicType) return "dynamic";
if (type is NeverType) return "Never";
if (type is FutureOrType) {
return "FutureOr<dynamic>";
}
if (type is FunctionType) {
// TODO(johnniwinther): Add naming scheme for function type literals.
// These currently only occur from kernel.
return '()->';
}
InterfaceType interface = type;
String name = uniqueNameForTypeConstantElement(
interface.element.library, interface.element);
// Type constants can currently only be raw types, so there is no point
// adding ground-term type parameters, as they would just be 'dynamic'.
// TODO(sra): Since the result string is used only in constructing constant
// names, it would result in more readable names if the final string was a
// legal JavaScript identifier.
if (interface.typeArguments.isEmpty) return name;
String arguments =
new List.filled(interface.typeArguments.length, 'dynamic').join(', ');
return '$name<$arguments>';
}
}
/// Returns a unique suffix for an intercepted accesses to [classes]. This is
/// used as the suffix for emitted interceptor methods and as the unique key
/// used to distinguish equivalences of sets of intercepted classes.
String suffixForGetInterceptor(CommonElements commonElements,
NativeData nativeData, 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();
}
/// 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 Namer _namer;
final JClosedWorld _closedWorld;
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._namer, this._closedWorld, this._hasher);
JElementEnvironment get _elementEnvironment =>
_closedWorld.elementEnvironment;
JFieldAnalysis get _fieldAnalysis => _closedWorld.fieldAnalysis;
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 visitSet(SetConstantValue constant, [_]) {
// TODO(9476): Incorporate type parameters into name.
addRoot('Set');
if (constant.length == 0) {
add('empty');
} else {
add(getHashTag(constant, 5));
}
}
@override
void visitMap(covariant constant_system.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.
_elementEnvironment.forEachInstanceField(constant.type.element,
(_, FieldEntity field) {
if (failed) return;
if (_fieldAnalysis.getFieldData(field).isElided) 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;
}
if (name == null) {
// e.g. DartType 'dynamic' has no element.
name = _namer.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 visitDummyInterceptor(DummyInterceptorConstantValue constant, [_]) {
add('dummy_interceptor');
}
@override
void visitUnreachable(UnreachableConstantValue constant, [_]) {
add('unreachable');
}
@override
void visitJsName(JsNameConstantValue constant, [_]) {
add('name');
}
@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 Namer _namer;
final JClosedWorld _closedWorld;
final Map<ConstantValue, int> _hashes = {};
ConstantCanonicalHasher(this._namer, this._closedWorld);
JElementEnvironment get _elementEnvironment =>
_closedWorld.elementEnvironment;
JFieldAnalysis get _fieldAnalysis => _closedWorld.fieldAnalysis;
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 visitSet(SetConstantValue constant, [_]) {
return _hashList(constant.length, constant.values);
}
@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);
_elementEnvironment.forEachInstanceField(constant.type.element,
(_, FieldEntity field) {
if (_fieldAnalysis.getFieldData(field).isElided) return;
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 = _namer.getTypeRepresentationForTypeConstant(type);
return _hashString(4, name);
}
@override
int visitInterceptor(InterceptorConstantValue constant, [_]) {
String typeName = constant.cls.name;
return _hashString(5, typeName);
}
@override
int visitDummyInterceptor(DummyInterceptorConstantValue constant, [_]) {
throw failedAt(
NO_LOCATION_SPANNABLE,
'DummyInterceptorConstantValue should never be named and '
'never be subconstant');
}
@override
int visitUnreachable(UnreachableConstantValue constant, [_]) {
throw failedAt(
NO_LOCATION_SPANNABLE,
'UnreachableConstantValue should never be named and '
'never be subconstant');
}
@override
int visitJsName(JsNameConstantValue constant, [_]) {
throw failedAt(
NO_LOCATION_SPANNABLE,
'JsNameConstantValue should never be named and '
'never be subconstant');
}
@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 {
StringBuffer sb;
FunctionTypeNamer();
String computeName(DartType type) {
sb = new StringBuffer();
visit(type);
return sb.toString();
}
@override
visit(DartType type, [_]) {
type.accept(this, null);
}
@override
visitType(DartType type, _) {}
@override
visitInterfaceType(InterfaceType type, _) {
sb.write(type.element.name);
}
@override
visitTypeVariableType(TypeVariableType type, _) {
sb.write(type.element.name);
}
bool _isSimpleFunctionType(FunctionType type) {
if (type.returnType is! DynamicType) return false;
if (!type.optionalParameterTypes.isEmpty) return false;
if (!type.namedParameterTypes.isEmpty) return false;
for (DartType parameter in type.parameterTypes) {
if (parameter is! DynamicType) return false;
}
return true;
}
@override
visitFunctionType(FunctionType type, _) {
if (_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 = {};
final Set<String> _usedNames = Set();
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);
}
}
/// Fixed names usage by the namer.
class FixedNames {
const FixedNames();
String get getterPrefix => r'get$';
String get setterPrefix => r'set$';
String get callPrefix => 'call';
String get callCatchAllName => r'call*';
String get callNameField => r'$callName';
String get defaultValuesField => r'$defaultValues';
String get deferredAction => r'$deferredAction';
String get operatorIsPrefix => r'$is';
String get operatorAsPrefix => r'$as';
String get operatorSignature => r'$signature';
String get requiredParameterField => r'$requiredArgCount';
String get rtiName => r'$ti';
}
/// Minified version of the fixed names usage by the namer.
// TODO(johnniwinther): This should implement [FixedNames] and minify all fixed
// names.
class MinifiedFixedNames extends FixedNames {
const MinifiedFixedNames();
@override
String get getterPrefix => 'g';
@override
String get setterPrefix => 's';
@override
String get callPrefix => ''; // this will create function names $<n>
@override
String get operatorIsPrefix => r'$i';
@override
String get operatorAsPrefix => r'$a';
@override
String get callCatchAllName => r'$C';
@override
String get requiredParameterField => r'$R';
@override
String get defaultValuesField => r'$D';
@override
String get operatorSignature => r'$S';
}
/// Namer interface that can be used in modular code generation.
abstract class ModularNamer {
FixedNames get fixedNames;
/// Returns a variable use for accessing [library].
///
/// This is one of the [reservedGlobalObjectNames]
jsAst.Expression readGlobalObjectForLibrary(LibraryEntity library);
/// Returns a variable use for accessing the class [element].
///
/// This is one of the [reservedGlobalObjectNames]
jsAst.Expression readGlobalObjectForClass(ClassEntity element);
/// Returns a variable use for accessing the type [element].
///
/// This is one of the [reservedGlobalObjectNames]
jsAst.Expression readGlobalObjectForType(Entity element);
/// Returns a variable use for accessing the member [element].
///
/// This is either the [staticStateHolder] or one of the
/// [reservedGlobalObjectNames]
jsAst.Expression readGlobalObjectForMember(MemberEntity 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);
/// 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);
/// 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);
/// Returns a name, the string of which is a globally unique key distinct from
/// other global property names.
///
/// The name is not necessarily a valid JavaScript identifier, so it needs to
/// be quoted.
jsAst.Name globalNameForInterfaceTypeVariable(
TypeVariableEntity typeVariable);
/// Returns the name for the instance field that holds runtime type arguments
/// on generic classes.
jsAst.Name get rtiFieldJsName;
/// 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);
/// Returns the JavaScript property name used to store an instance field.
jsAst.Name instanceFieldPropertyName(FieldEntity element);
/// Annotated name for [method] encoding arity and named parameters.
jsAst.Name instanceMethodName(FunctionEntity method);
/// 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);
/// Annotated name for the member being invoked by [selector].
jsAst.Name invocationName(Selector selector);
/// Property name used for a specialization of `getInterceptor`.
///
/// js_runtime contains a top-level `getInterceptor` method. The
/// specializations have the same name, but with a suffix to avoid name
/// collisions.
jsAst.Name nameForGetInterceptor(Set<ClassEntity> classes);
/// Property name used for the one-shot interceptor method for the given
/// [selector] and return-type specialization.
jsAst.Name nameForOneShotInterceptor(
Selector selector, Set<ClassEntity> classes);
/// 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);
/// 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);
/// Returns the name of the `isX` property for classes that implement
/// [element].
jsAst.Name operatorIs(ClassEntity element);
/// Return the name of the `isX` property for classes that implement [type].
jsAst.Name operatorIsType(DartType type);
/// Returns the name of the `asX` function for classes that implement the
/// generic class [element].
jsAst.Name substitutionName(ClassEntity element);
/// Returns the name of the lazy initializer for the static field [element].
jsAst.Name lazyInitializerName(FieldEntity element);
/// Returns the name of the closure of the static method [element].
jsAst.Name staticClosureName(FunctionEntity 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_);
/// The prefix used for encoding async properties.
final String asyncPrefix = r"$async$";
/// Returns the name for the holder of static state.
///
/// This is used for mutable static fields.
final String staticStateHolder = r'$';
jsAst.Name _literalAsyncPrefix;
ModularNamer() {
_literalAsyncPrefix = new StringBackedName(asyncPrefix);
}
/// 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";
}
/// Returns the name for the async body of the method with the [original]
/// name.
jsAst.Name deriveAsyncBodyName(jsAst.Name original) {
return new AsyncName(_literalAsyncPrefix, original);
}
/// Returns the label name for [label] used as a break target.
String breakLabelName(LabelDefinition label) {
return '\$${label.labelName}\$${label.target.nestingLevel}';
}
/// Returns the label name for the implicit break label needed for the jump
/// [target].
String implicitBreakLabelName(JumpTarget target) {
return '\$${target.nestingLevel}';
}
/// Returns the label name for [label] used as a continue target.
///
/// 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}';
}
/// Returns the label name for the implicit continue label needed for the jump
/// [target].
String implicitContinueLabelName(JumpTarget target) {
return 'c\$${target.nestingLevel}';
}
Set<String> _jsVariableReservedCache = null;
/// Names that cannot be used by local variables and parameters.
Set<String> get _jsVariableReserved {
if (_jsVariableReservedCache == null) {
_jsVariableReservedCache = new Set<String>();
_jsVariableReservedCache.addAll(Namer.javaScriptKeywords);
_jsVariableReservedCache.addAll(Namer.reservedPropertySymbols);
_jsVariableReservedCache.addAll(Namer.reservedGlobalSymbols);
_jsVariableReservedCache.addAll(Namer.reservedGlobalObjectNames);
// 26 letters in the alphabet, 25 not counting I.
assert(Namer.reservedGlobalObjectNames.length == 25);
_jsVariableReservedCache.addAll(Namer.reservedGlobalHelperFunctions);
}
return _jsVariableReservedCache;
}
/// 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;
}
CommonElements get _commonElements;
/// 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(fixedNames.getterPrefix);
case JsGetName.SETTER_PREFIX:
return asName(fixedNames.setterPrefix);
case JsGetName.CALL_PREFIX:
return asName(fixedNames.callPrefix);
case JsGetName.CALL_PREFIX0:
return asName('${fixedNames.callPrefix}\$0');
case JsGetName.CALL_PREFIX1:
return asName('${fixedNames.callPrefix}\$1');
case JsGetName.CALL_PREFIX2:
return asName('${fixedNames.callPrefix}\$2');
case JsGetName.CALL_PREFIX3:
return asName('${fixedNames.callPrefix}\$3');
case JsGetName.CALL_PREFIX4:
return asName('${fixedNames.callPrefix}\$4');
case JsGetName.CALL_PREFIX5:
return asName('${fixedNames.callPrefix}\$5');
case JsGetName.CALL_CATCH_ALL:
return asName(fixedNames.callCatchAllName);
case JsGetName.REQUIRED_PARAMETER_PROPERTY:
return asName(fixedNames.requiredParameterField);
case JsGetName.DEFAULT_VALUES_PROPERTY:
return asName(fixedNames.defaultValuesField);
case JsGetName.CALL_NAME_PROPERTY:
return asName(fixedNames.callNameField);
case JsGetName.DEFERRED_ACTION_PROPERTY:
return asName(fixedNames.deferredAction);
case JsGetName.OPERATOR_AS_PREFIX:
return asName(fixedNames.operatorAsPrefix);
case JsGetName.OPERATOR_IS_PREFIX:
return asName(fixedNames.operatorIsPrefix);
case JsGetName.SIGNATURE_NAME:
return asName(fixedNames.operatorSignature);
case JsGetName.RTI_NAME:
return asName(fixedNames.rtiName);
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.FUTURE_CLASS_TYPE_NAME:
return runtimeTypeName(_commonElements.futureClass);
case JsGetName.RTI_FIELD_AS:
return instanceFieldPropertyName(_commonElements.rtiAsField);
case JsGetName.RTI_FIELD_IS:
return instanceFieldPropertyName(_commonElements.rtiIsField);
default:
throw failedAt(spannable, 'Error: Namer has no name for "$name".');
}
}
}
class ModularNamerImpl extends ModularNamer {
final CodegenRegistry _registry;
@override
final FixedNames fixedNames;
@override
final CommonElements _commonElements;
ModularNamerImpl(this._registry, this._commonElements, this.fixedNames);
@override
jsAst.Name get rtiFieldJsName {
jsAst.Name name = new ModularName(ModularNameKind.rtiField);
_registry.registerModularName(name);
return name;
}
@override
jsAst.Name runtimeTypeName(Entity element) {
jsAst.Name name =
new ModularName(ModularNameKind.runtimeTypeName, data: element);
_registry.registerModularName(name);
return name;
}
@override
jsAst.Name className(ClassEntity element) {
jsAst.Name name = new ModularName(ModularNameKind.className, data: element);
_registry.registerModularName(name);
return name;
}
@override
jsAst.Expression readGlobalObjectForLibrary(LibraryEntity library) {
jsAst.Expression expression = new ModularExpression(
ModularExpressionKind.globalObjectForLibrary, library);
_registry.registerModularExpression(expression);
return expression;
}
@override
jsAst.Expression readGlobalObjectForClass(ClassEntity element) {
jsAst.Expression expression = new ModularExpression(
ModularExpressionKind.globalObjectForClass, element);
_registry.registerModularExpression(expression);
return expression;
}
@override
jsAst.Expression readGlobalObjectForType(Entity element) {
jsAst.Expression expression = new ModularExpression(
ModularExpressionKind.globalObjectForType, element);
_registry.registerModularExpression(expression);
return expression;
}
@override
jsAst.Expression readGlobalObjectForMember(MemberEntity element) {
jsAst.Expression expression = new ModularExpression(
ModularExpressionKind.globalObjectForMember, element);
_registry.registerModularExpression(expression);
return expression;
}
@override
jsAst.Name aliasedSuperMemberPropertyName(MemberEntity member) {
jsAst.Name name =
new ModularName(ModularNameKind.aliasedSuperMember, data: member);
_registry.registerModularName(name);
return name;
}
@override
jsAst.Name staticClosureName(FunctionEntity element) {
jsAst.Name name =
new ModularName(ModularNameKind.staticClosure, data: element);
_registry.registerModularName(name);
return name;
}
@override
jsAst.Name methodPropertyName(FunctionEntity method) {
jsAst.Name name =
new ModularName(ModularNameKind.methodProperty, data: method);
_registry.registerModularName(name);
return name;
}
@override
jsAst.Name instanceFieldPropertyName(FieldEntity element) {
jsAst.Name name =
new ModularName(ModularNameKind.instanceField, data: element);
_registry.registerModularName(name);
return name;
}
@override
jsAst.Name operatorIsType(DartType type) {
jsAst.Name name =
new ModularName(ModularNameKind.operatorIsType, data: type);
_registry.registerModularName(name);
return name;
}
@override
jsAst.Name instanceMethodName(FunctionEntity method) {
jsAst.Name name =
new ModularName(ModularNameKind.instanceMethod, data: method);
_registry.registerModularName(name);
return name;
}
@override
jsAst.Name invocationName(Selector selector) {
jsAst.Name name =
new ModularName(ModularNameKind.invocation, data: selector);
_registry.registerModularName(name);
return name;
}
@override
jsAst.Name lazyInitializerName(FieldEntity element) {
jsAst.Name name =
new ModularName(ModularNameKind.lazyInitializer, data: element);
_registry.registerModularName(name);
return name;
}
@override
jsAst.Name operatorIs(ClassEntity element) {
jsAst.Name name =
new ModularName(ModularNameKind.operatorIs, data: element);
_registry.registerModularName(name);
return name;
}
@override
jsAst.Name globalPropertyNameForType(Entity element) {
jsAst.Name name = new ModularName(ModularNameKind.globalPropertyNameForType,
data: element);
_registry.registerModularName(name);
return name;
}
@override
jsAst.Name globalPropertyNameForClass(ClassEntity element) {
jsAst.Name name = new ModularName(
ModularNameKind.globalPropertyNameForClass,
data: element);
_registry.registerModularName(name);
return name;
}
@override
jsAst.Name globalPropertyNameForMember(MemberEntity element) {
jsAst.Name name = new ModularName(
ModularNameKind.globalPropertyNameForMember,
data: element);
_registry.registerModularName(name);
return name;
}
@override
jsAst.Name globalNameForInterfaceTypeVariable(TypeVariableEntity element) {
jsAst.Name name = new ModularName(
ModularNameKind.globalNameForInterfaceTypeVariable,
data: element);
_registry.registerModularName(name);
return name;
}
@override
jsAst.Name nameForGetInterceptor(Set<ClassEntity> classes) {
jsAst.Name name =
new ModularName(ModularNameKind.nameForGetInterceptor, set: classes);
_registry.registerModularName(name);
return name;
}
@override
jsAst.Name nameForOneShotInterceptor(
Selector selector, Set<ClassEntity> classes) {
jsAst.Name name = new ModularName(ModularNameKind.nameForOneShotInterceptor,
data: selector, set: classes);
_registry.registerModularName(name);
return name;
}
@override
jsAst.Name asName(String text) {
jsAst.Name name = new ModularName(ModularNameKind.asName, data: text);
_registry.registerModularName(name);
return name;
}
@override
jsAst.Name substitutionName(ClassEntity element) {
jsAst.Name name =
new ModularName(ModularNameKind.substitution, data: element);
_registry.registerModularName(name);
return name;
}
}