Index: pkg/compiler/lib/src/js_backend/namer.dart |
diff --git a/pkg/compiler/lib/src/js_backend/namer.dart b/pkg/compiler/lib/src/js_backend/namer.dart |
index 7508513cf928d26777bb6a429275d172d3920e01..7dccb164081b2ba96add783f06e8ae87c365c780 100644 |
--- a/pkg/compiler/lib/src/js_backend/namer.dart |
+++ b/pkg/compiler/lib/src/js_backend/namer.dart |
@@ -6,10 +6,98 @@ part of js_backend; |
/** |
* Assigns JavaScript identifiers to Dart variables, class-names and members. |
+ * |
+ * Names are generated through three stages: |
+ * |
+ * 1. Original names and proposed names |
+ * 2. Disambiguated names (also known as "mangled names") |
+ * 3. Annotated names |
+ * |
+ * Original names are names taken directly from the input. |
+ * |
+ * Proposed names are either original names or synthesized names for input |
+ * elements that do not have original names. |
+ * |
+ * Disambiguated names are derived from the above, but are mangled to ensure |
+ * uniqueness within some namespace (e.g. as fields on the same JS object). |
+ * In [MinifyNamer], disambiguated names are also minified. |
+ * |
+ * Annotated names are names generated from a disambiguated name. Annnotated |
+ * names must be computable at runtime by prefixing/suffixing constant strings |
+ * onto the disambiguated name. |
+ * |
+ * For example, some entity called `x` might be associated with these names: |
+ * |
+ * Original name: `x` |
+ * |
+ * Disambiguated name: `x1` (if something else was called `x`) |
+ * |
+ * Annotated names: `x1` (field name) |
+ * `get$x1` (getter name) |
+ * `set$x1` (setter name) |
+ * `x1$2` (if `x` is a method with 2 parameters) |
+ * |
+ * 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 a annotated name. |
floitsch
2015/01/30 21:03:38
as an annotated name.
asgerf
2015/02/03 17:39:11
Done.
|
+ * |
+ * 1. Multiple annotated names exist per method name, encoding arity and named |
+ * parameters with the pattern: |
+ * |
+ * <NAME>$<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 this convention is not encapsulated in the [Namer], and is |
+ * hardcoded into other components, such as [Element] and [Selector]. |
floitsch
2015/01/30 21:03:36
:(
asgerf
2015/02/03 17:39:12
Clarified that it's just the ordering of parameter
floitsch
2015/02/04 00:10:10
ok. that's not a big deal.
|
+ * |
+ * 2. The getter/setter for a field, or tear-off getter for a method: |
+ * |
+ * 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 implements ClosureNamer { |
- static const javaScriptKeywords = const <String>[ |
+ static const List<String> javaScriptKeywords = const <String>[ |
// These are current keywords. |
"break", "delete", "function", "return", "typeof", "case", "do", "if", |
"switch", "var", "catch", "else", "in", "this", "void", "continue", |
@@ -24,11 +112,11 @@ class Namer implements ClosureNamer { |
"long", "short", "volatile" |
]; |
- static const reservedPropertySymbols = |
+ static const List<String> reservedPropertySymbols = |
const <String>["__proto__", "prototype", "constructor", "call"]; |
// Symbols that we might be using in our JS snippets. |
- static const reservedGlobalSymbols = const <String>[ |
+ 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) |
@@ -148,7 +236,7 @@ class Namer implements ClosureNamer { |
"JavaArray", "JavaMember", |
]; |
- static const reservedGlobalObjectNames = const <String>[ |
+ static const List<String> reservedGlobalObjectNames = const <String>[ |
"A", |
"B", |
"C", // Global object for *C*onstants. |
@@ -177,12 +265,13 @@ class Namer implements ClosureNamer { |
"Z", |
]; |
- static const reservedGlobalHelperFunctions = const <String>[ |
+ static const List<String> reservedGlobalHelperFunctions = const <String>[ |
"init", |
"Isolate", |
]; |
- static final userGlobalObjects = new List.from(reservedGlobalObjectNames) |
+ static final List<String> userGlobalObjects = |
+ new List.from(reservedGlobalObjectNames) |
..remove('C') |
..remove('H') |
..remove('J') |
@@ -252,10 +341,11 @@ class Namer implements ClosureNamer { |
final Set<String> usedGlobalNames; |
final Set<String> usedInstanceNames; |
- final Map<String, String> globalNameMap; |
+ final Map<String, String> internGlobals; |
floitsch
2015/01/30 21:03:36
add comment.
internGlobals -> internalGlobals?
Ma
asgerf
2015/02/03 17:39:12
Done.
|
final Map<String, String> suggestedGlobalNames; |
floitsch
2015/01/30 21:03:38
I would group the fields:
globals
usedGlobalNames
asgerf
2015/02/03 17:39:12
Grouped and renamed, to be more streamlined. I add
|
- final Map<String, String> instanceNameMap; |
+ final Map<LibraryElement, Map<String, String>> instanceMembers; |
floitsch
2015/01/30 21:03:37
Add a comment explaining that `null` is used as ke
asgerf
2015/02/03 17:39:13
The map works differently now, see disambiguateMem
|
final Map<String, String> suggestedInstanceNames; |
+ final Map<Element, String> directAccessMap = <Element, String>{}; |
floitsch
2015/01/30 21:03:37
comment.
asgerf
2015/02/03 17:39:12
see above.
|
final Map<String, String> operatorNameMap; |
final Map<String, int> popularNameCounters; |
@@ -274,9 +364,9 @@ class Namer implements ClosureNamer { |
shortPrivateNameOwners = new Map<String, LibraryElement>(), |
usedGlobalNames = new Set<String>(), |
usedInstanceNames = new Set<String>(), |
- instanceNameMap = new Map<String, String>(), |
+ instanceMembers = new Map<LibraryElement, Map<String, String>>(), |
operatorNameMap = new Map<String, String>(), |
- globalNameMap = new Map<String, String>(), |
+ internGlobals = new Map<String, String>(), |
suggestedGlobalNames = new Map<String, String>(), |
suggestedInstanceNames = new Map<String, String>(), |
popularNameCounters = new Map<String, int>(), |
@@ -313,6 +403,9 @@ class Namer implements ClosureNamer { |
} |
} |
+ /// Disambiguated name for [constant]. |
+ /// |
+ /// Unique within the global-member namespace. |
floitsch
2015/01/30 21:03:37
Is that necessary?
After all, we always put consta
asgerf
2015/02/03 17:39:13
You are right, but this is how it worked to begin
floitsch
2015/02/04 00:10:10
TODO is fine.
|
String constantName(ConstantValue constant) { |
// In the current implementation it doesn't make sense to give names to |
// function constants since the function-implementation itself serves as |
@@ -328,7 +421,7 @@ class Namer implements ClosureNamer { |
return result; |
} |
- // The long name is unminified and may have collisions. |
+ /// Proposed name for [constant]. |
String constantLongName(ConstantValue constant) { |
floitsch
2015/01/30 21:03:37
This looks like a function that shouldn't be calle
asgerf
2015/02/03 17:39:13
The emitters currently call it for some sort of co
floitsch
2015/02/04 00:10:11
Acknowledged.
|
String longName = constantLongNames[constant]; |
if (longName == null) { |
@@ -358,107 +451,94 @@ class Namer implements ClosureNamer { |
} |
/** |
- * If the [name] is not private returns [:name:]. Otherwise |
- * mangles the [name] so that each library has a unique name. |
+ * 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(LibraryElement library, String name) { |
+ String privateName(LibraryElement library, String originalName) { |
// Public names are easy. |
- String nameString = name; |
- if (!isPrivateName(name)) return nameString; |
+ if (!isPrivateName(originalName)) return originalName; |
// The first library asking for a short private name wins. |
LibraryElement owner = |
- shortPrivateNameOwners.putIfAbsent(nameString, () => library); |
+ shortPrivateNameOwners.putIfAbsent(originalName, () => library); |
- if (owner == library && !nameString.contains('\$')) { |
+ if (owner == library && !originalName.contains('\$')) { |
// Since the name doesn't contain $ it doesn't clash with any |
// of the private names that have the library name as the prefix. |
- return nameString; |
+ return originalName; |
} else { |
// Make sure to return a private name that starts with _ so it |
// cannot clash with any public names. |
- String libraryName = getNameOfLibrary(library); |
- return '_$libraryName\$$nameString'; |
+ String libraryName = disambiguateGlobal(library); |
+ return '_$libraryName\$${originalName}'; |
floitsch
2015/01/30 21:03:38
Add comment why this is not unique.
asgerf
2015/02/03 17:39:13
Done.
|
} |
} |
- String instanceMethodName(FunctionElement element) { |
- // TODO(ahe): Could this be: return invocationName(new |
- // Selector.fromElement(element))? |
- String elementName = element.name; |
- String name = operatorNameToIdentifier(elementName); |
- if (name != elementName) return getMappedOperatorName(name); |
- |
- LibraryElement library = element.library; |
- if (element.isGenerativeConstructorBody) { |
- name = Elements.reconstructConstructorNameSourceString(element); |
- } |
- FunctionSignature signature = element.functionSignature; |
- // We don't mangle the closure invoking function name because it |
- // is generated by string concatenation in applyFunction from |
- // js_helper.dart. To keep code size down, we potentially shorten |
- // the prefix though. |
- String methodName; |
- if (name == closureInvocationSelectorName) { |
- methodName = '$callPrefix\$${signature.parameterCount}'; |
- } else { |
- methodName = '${privateName(library, name)}\$${signature.parameterCount}'; |
+ /// Annotated name for [method] encoding arity and named parameters. |
+ String instanceMethodName(FunctionElement method) { |
+ if (method.isGenerativeConstructorBody) { |
+ return disambiguateDirectAccess(method, |
+ () => Elements.reconstructConstructorNameSourceString(method)); |
} |
- if (signature.optionalParametersAreNamed && |
- !signature.optionalParameters.isEmpty) { |
- StringBuffer buffer = new StringBuffer(); |
- signature.orderedOptionalParameters.forEach((Element element) { |
- buffer.write('\$${safeName(element.name)}'); |
- }); |
- methodName = '$methodName$buffer'; |
+ return invocationName(new Selector.fromElement(method)); |
+ } |
+ |
+ /// Annotated name for a public method with the given [originalName] |
+ /// and [arity] and no named parameters. |
floitsch
2015/01/30 21:03:36
Maybe mention that it handles operators?
asgerf
2015/02/03 17:39:12
Not anymore.
|
+ String publicInstanceMethodNameByArity(String originalName, int arity) { |
+ String opName = operatorNameToIdentifier(originalName); |
+ if (opName != originalName) return disambiguateOperator(opName); |
+ assert(!isPrivateName(originalName)); |
+ String disambiguatedName = disambiguateMember(null, originalName); |
+ Selector selector = new Selector.call(originalName, null, arity); |
+ return deriveMethodName(disambiguatedName, selector); |
+ } |
+ |
+ /// Returns the annotated name for a method with the given name, |
+ /// encoding arity and named parameters with the pattern: |
+ /// |
+ /// <NAME>$<N>$namedParam1...$namedParam<M> |
+ /// |
+ String deriveMethodName(String disambiguatedName, Selector selector) { |
+ assert(selector.isCall); |
+ // TODO(asgerf): Avoid clash when named parameters contain '$' symbols. |
floitsch
2015/01/30 21:03:37
Create a failing test.
asgerf
2015/02/03 17:39:12
Done. And the issue has been fixed.
|
+ StringBuffer buffer = new StringBuffer(); |
+ for (String argumentName in selector.getOrderedNamedArguments()) { |
+ buffer.write('\$${safeName(argumentName)}'); |
} |
- if (name == closureInvocationSelectorName) return methodName; |
- return getMappedInstanceName(methodName); |
- } |
- |
- String publicInstanceMethodNameByArity(String name, int arity) { |
- String newName = operatorNameToIdentifier(name); |
- if (newName != name) return getMappedOperatorName(newName); |
- assert(!isPrivateName(name)); |
- // We don't mangle the closure invoking function name because it |
- // is generated by string concatenation in applyFunction from |
- // js_helper.dart. To keep code size down, we potentially shorten |
- // the prefix though. |
- if (name == closureInvocationSelectorName) return '$callPrefix\$$arity'; |
- |
- return getMappedInstanceName('$name\$$arity'); |
+ return '$disambiguatedName\$${selector.argumentCount}$buffer'; |
} |
+ /// Annotated name for the member being invoked by [selector]. |
String invocationName(Selector selector) { |
- if (selector.isGetter) { |
- String proposedName = privateName(selector.library, selector.name); |
- return '$getterPrefix${getMappedInstanceName(proposedName)}'; |
- } else if (selector.isSetter) { |
- String proposedName = privateName(selector.library, selector.name); |
- return '$setterPrefix${getMappedInstanceName(proposedName)}'; |
- } else { |
- String name = selector.name; |
- if (selector.kind == SelectorKind.OPERATOR |
- || selector.kind == SelectorKind.INDEX) { |
- name = operatorNameToIdentifier(name); |
- assert(name != selector.name); |
- return getMappedOperatorName(name); |
- } |
- assert(name == operatorNameToIdentifier(name)); |
- StringBuffer buffer = new StringBuffer(); |
- for (String argumentName in selector.getOrderedNamedArguments()) { |
- buffer.write('\$${safeName(argumentName)}'); |
- } |
- String suffix = '\$${selector.argumentCount}$buffer'; |
- // We don't mangle the closure invoking function name because it |
- // is generated by string concatenation in applyFunction from |
- // js_helper.dart. We potentially shorten the prefix though. |
- if (selector.isClosureCall) { |
- return "$callPrefix$suffix"; |
- } else { |
- String proposedName = privateName(selector.library, name); |
- return getMappedInstanceName('$proposedName$suffix'); |
- } |
+ LibraryElement library = selector.library; |
+ switch (selector.kind) { |
+ case SelectorKind.GETTER: |
+ String disambiguatedName = disambiguateMember(library, selector.name); |
+ return deriveGetterName(disambiguatedName); |
+ |
+ case SelectorKind.SETTER: |
+ String disambiguatedName = disambiguateMember(library, selector.name); |
+ return deriveSetterName(disambiguatedName); |
+ |
+ case SelectorKind.OPERATOR: |
+ case SelectorKind.INDEX: |
+ String operatorIdentifier = operatorNameToIdentifier(selector.name); |
+ String disambiguatedName = disambiguateOperator(operatorIdentifier); |
+ return disambiguatedName; // Operators are not annotated. |
+ |
+ case SelectorKind.CALL: |
+ String disambiguatedName = disambiguateMember(library, selector.name); |
+ return deriveMethodName(disambiguatedName, selector); |
+ |
+ default: throw 'Unexpected selector kind: ${selector.kind}'; |
floitsch
2015/01/30 21:03:37
throw an internal error. (we tend to avoid throwin
asgerf
2015/02/03 17:39:13
Done.
|
} |
} |
@@ -469,13 +549,13 @@ class Namer implements ClosureNamer { |
=> invocationName(selector); |
/** |
- * Returns name of accessor (root to getter and setter) for a static or |
- * instance field. |
+ * Returns the disambiguated name for the given field, used for constructing |
+ * the getter and setter names. |
*/ |
String fieldAccessorName(Element element) { |
return element.isInstanceMember |
- ? instanceFieldAccessorName(element) |
- : getNameOfField(element); |
+ ? disambiguateMember(element.library, element.name) |
+ : disambiguateGlobal(element); |
} |
/** |
@@ -485,126 +565,233 @@ class Namer implements ClosureNamer { |
String fieldPropertyName(Element element) { |
return element.isInstanceMember |
? instanceFieldPropertyName(element) |
- : getNameOfField(element); |
+ : disambiguateGlobal(element); |
} |
/** |
- * Returns name of accessor (root to getter and setter) for an instance field. |
+ * Returns name of the JavaScript property used to store the given type |
+ * variable. |
floitsch
2015/01/30 21:03:38
Also mention that this is inside a class and thus
asgerf
2015/02/03 17:39:13
Done.
|
*/ |
- String instanceFieldAccessorName(Element element) { |
- String proposedName = privateName(element.library, element.name); |
- return getMappedInstanceName(proposedName); |
+ String readTypeVariableName(TypeVariableElement element) { |
floitsch
2015/01/30 21:03:37
Why is this prefixed "read" ?
asgerf
2015/02/03 17:39:13
Don't know. Changed to typeVariableName.
|
+ return disambiguateDirectAccess(element, () => element.name); |
} |
- String readTypeVariableName(TypeVariableElement element) { |
- return '\$tv_${instanceFieldAccessorName(element)}'; |
+ /** |
+ * Returns a JavaScript property name used to store [element] on one |
+ * of the global objects. |
+ * |
+ * Should be used together with [globalObjectFor], which denotes the object |
+ * on which the returned property name should be used. |
+ */ |
+ String globalPropertyName(Element element) { |
+ return disambiguateGlobal(element); |
} |
/** |
- * Returns name of the JavaScript property used to store an instance field. |
+ * Returns the JavaScript property name used to store an instance field. |
*/ |
String instanceFieldPropertyName(Element element) { |
if (element.hasFixedBackendName) { |
return element.fixedBackendName; |
floitsch
2015/01/30 21:03:36
Does this potentially lead to clashes?
What if th
asgerf
2015/02/03 17:39:12
Turns out clashes are possible. Added custom_eleme
|
} |
- // If a class is used anywhere as a mixin, we must make the name unique so |
- // that it does not accidentally shadow. Also, the mixin name must be |
- // constant over all mixins. |
+ |
+ // If the name of the field might clash with another field, |
+ // use a mangled field name to avoid potential clashes. |
ClassWorld classWorld = compiler.world; |
if (classWorld.isUsedAsMixin(element.enclosingClass) || |
shadowingAnotherField(element)) { |
- // Construct a new name for the element based on the library and class it |
- // is in. The name here is not important, we just need to make sure it is |
- // unique. If we are minifying, we actually construct the name from the |
- // minified version of the class name, but the result is minified once |
- // again, so that is not visible in the end result. |
- String libraryName = getNameOfLibrary(element.library); |
- String className = getNameOfClass(element.enclosingClass); |
- String instanceName = privateName(element.library, element.name); |
- return getMappedInstanceName('$libraryName\$$className\$$instanceName'); |
+ return disambiguateDirectAccess(element, |
+ () => '${element.enclosingClass.name}_${element.name}'); |
floitsch
2015/01/30 21:03:37
Is this enough?
What if I have a mixin class A_b w
asgerf
2015/02/03 17:39:13
The string we're building is only a proposed name.
|
} |
- String proposedName = privateName(element.library, element.name); |
- return getMappedInstanceName(proposedName); |
+ // No inheritance-related name clashes exist, so the accessor and property |
floitsch
2015/01/30 21:03:37
I don't understand this comment.
Do you mean: no i
asgerf
2015/02/03 17:39:12
Done following offline discussion.
|
+ // name can be shared. This generates nicer field names since otherwise |
+ // one of them would have to be mangled. |
+ return disambiguateMember(element.library, element.name); |
} |
- |
bool shadowingAnotherField(Element element) { |
floitsch
2015/01/30 21:03:37
isShadowingASuperField ?
asgerf
2015/02/03 17:39:13
Done.
|
return element.enclosingClass.hasFieldShadowedBy(element); |
} |
- String setterName(Element element) { |
+ /// Annotated name for the setter of [element]. |
+ String setterForElement(Element element) { |
// We dynamically create setters from the field-name. The setter name must |
// therefore be derived from the instance field-name. |
- LibraryElement library = element.library; |
- String name = getMappedInstanceName(privateName(library, element.name)); |
- return '$setterPrefix$name'; |
+ String name = disambiguateMember(element.library, element.name); |
+ return deriveSetterName(name); |
} |
- String setterNameFromAccessorName(String name) { |
+ /// Annotated name for the setter of any member with [disambiguatedName]. |
+ String deriveSetterName(String disambiguatedName) { |
// We dynamically create setters from the field-name. The setter name must |
// therefore be derived from the instance field-name. |
- return '$setterPrefix$name'; |
+ return '$setterPrefix$disambiguatedName'; |
} |
- String getterNameFromAccessorName(String name) { |
+ /// Annotated name for the setter of any member with [disambiguatedName]. |
+ String deriveGetterName(String disambiguatedName) { |
// We dynamically create getters from the field-name. The getter name must |
// therefore be derived from the instance field-name. |
- return '$getterPrefix$name'; |
+ return '$getterPrefix$disambiguatedName'; |
} |
- String getterName(Element element) { |
+ /// Annotated name for the getter of [element]. |
+ String getterForElement(Element element) { |
floitsch
2015/01/30 21:03:38
nit: either put getter and setter next to each oth
asgerf
2015/02/03 17:39:12
I agree. I kept it like this to avoid messing up t
|
// We dynamically create getters from the field-name. The getter name must |
// therefore be derived from the instance field-name. |
- LibraryElement library = element.library; |
- String name = getMappedInstanceName(privateName(library, element.name)); |
- return '$getterPrefix$name'; |
+ String name = disambiguateMember(element.library, element.name); |
+ return deriveGetterName(name); |
} |
- String getMappedGlobalName(String proposedName, {bool ensureSafe: true}) { |
- var newName = globalNameMap[proposedName]; |
+ /// Property name for the getter of an instance member with [originalName] |
+ /// in [library]. |
+ /// |
+ /// [library] may be `null` if [originalName] is known to be public. |
+ String getterForMember(LibraryElement library, String originalName) { |
+ String disambiguatedName = disambiguateMember(library, originalName); |
+ return deriveGetterName(disambiguatedName); |
+ } |
+ |
+ /// Disambiguated name for a compiler-owned global variable. |
+ /// |
+ /// The resulting name is unique within the global-member namespace. |
+ String disambiguateInternGlobal(String name, {bool ensureSafe: true}) { |
floitsch
2015/01/30 21:03:36
disambiguateInternalGlobal ?
disambiguateCompilerG
floitsch
2015/01/30 21:03:36
Comment what "ensureSafe" means.
|
+ String newName = internGlobals[name]; |
if (newName == null) { |
- newName = getFreshName(proposedName, usedGlobalNames, |
+ newName = getFreshName(name, usedGlobalNames, |
suggestedGlobalNames, ensureSafe: ensureSafe); |
- globalNameMap[proposedName] = newName; |
+ internGlobals[name] = newName; |
+ } |
+ return newName; |
+ } |
+ |
+ /// Returns the property name to use for a compiler-owner global variable, |
+ /// i.e. one that does not correspond to any element but is used as a utility |
+ /// global by code generation. |
+ /// |
+ /// [name] functions as both the proposed name for the global, and as a key |
+ /// identifying the global. The [name] should not contain `$` symbols, since |
floitsch
2015/01/30 21:03:36
Make this a "must" and assert it.
asgerf
2015/02/03 17:39:13
Done.
|
+ /// the [Namer] uses those names internally. |
+ /// |
+ /// This provides an easy mechanism for avoiding a name-clash with user-space |
+ /// globals, although the callers of must still take care not to accidentally |
floitsch
2015/01/30 21:03:36
-of-
asgerf
2015/02/03 17:39:12
Done.
|
+ /// pass in the same [name] for two different intern globals. |
+ String internGlobal(String name, {bool ensureSafe: true}) { |
floitsch
2015/01/30 21:03:37
internGlobal -> internalGlobal ?
asgerf
2015/02/03 17:39:13
Done.
|
+ return disambiguateInternGlobal(name, ensureSafe: ensureSafe); |
+ } |
+ |
+ /// Returns the disambiguated name for a top-level or static element. |
+ /// |
+ /// The resulting name is unique within the global-member namespace. |
floitsch
2015/01/30 21:03:37
Same as for the constant: we might be able to only
asgerf
2015/02/03 17:39:12
Again, probably yes. But this CL is big enough alr
floitsch
2015/02/04 00:10:10
Didn't expect more than a TODO.
|
+ String disambiguateGlobal(Element element) { |
+ element = element.declaration; |
+ String newName = globals[element]; |
+ if (newName == null) { |
+ String proposedName = _proposeNameForGlobal(element); |
+ newName = getFreshName(proposedName, usedGlobalNames, |
+ suggestedGlobalNames, ensureSafe: true); |
+ globals[element] = newName; |
} |
return newName; |
} |
- String getMappedInstanceName(String proposedName) { |
- var newName = instanceNameMap[proposedName]; |
+ /// 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 names of accessors (getters and |
+ /// setters) and annotated method names (encoding arity and named parameters). |
+ /// |
+ /// The resulting name, and its associated annotated names, are unique within |
+ /// the instance-member namespace. |
+ String disambiguateMember(LibraryElement library, String originalName) { |
+ if (originalName == closureInvocationSelectorName) { |
floitsch
2015/01/30 21:03:36
assert that library is not null if the originalNam
asgerf
2015/02/03 17:39:11
Done.
|
+ // We hardcode the disambiguated name of 'call'. |
+ return callPrefix; |
+ } |
+ if (!isPrivateName(originalName)) { |
+ library = null; |
+ } |
+ Map<String, String> nameMap = |
+ instanceMembers.putIfAbsent(library, () => new Map<String,String>()); |
+ String newName = nameMap[originalName]; |
+ if (newName == null) { |
+ String proposedName = privateName(library, originalName); |
+ newName = getFreshName(proposedName, |
+ usedInstanceNames, suggestedInstanceNames, |
+ ensureSafe: true, |
+ sanitizeForAnnotations: true); |
+ nameMap[originalName] = newName; |
+ } |
+ return newName; |
+ } |
+ |
+ /// Disambiguated name unique to [element]. |
+ /// |
+ /// This is used as the property name for fields, type variables, |
+ /// constructor bodies, and super-accessors. |
+ /// |
+ /// The resulting name is unique within the instance-member namespace. |
+ String disambiguateDirectAccess(Element element, String proposeName()) { |
floitsch
2015/01/30 21:03:38
Explain how this is different from disambiguateMem
asgerf
2015/02/03 17:39:12
I've changed it to disambiguateInternalMember. I h
|
+ String newName = directAccessMap[element]; |
if (newName == null) { |
- newName = getFreshName(proposedName, usedInstanceNames, |
- suggestedInstanceNames, ensureSafe: true); |
- instanceNameMap[proposedName] = newName; |
+ String name = proposeName(); |
+ newName = getFreshName(name, |
+ usedInstanceNames, suggestedInstanceNames, |
+ ensureSafe: true, |
+ sanitizeForAnnotations: true); |
+ directAccessMap[element] = newName; |
} |
return newName; |
} |
- String getMappedOperatorName(String proposedName) { |
- var newName = operatorNameMap[proposedName]; |
+ /// Disambiguated name for the given operator. |
+ /// |
+ /// [operatorIdentifier] must be the operator's identifier, e.g. |
+ /// `$add` and not `+`. |
+ /// |
+ /// The resulting name is unique within the instance-member namespace. |
+ String disambiguateOperator(String operatorIdentifier) { |
+ String newName = operatorNameMap[operatorIdentifier]; |
if (newName == null) { |
- newName = getFreshName(proposedName, usedInstanceNames, |
+ newName = getFreshName(operatorIdentifier, usedInstanceNames, |
suggestedInstanceNames, ensureSafe: false); |
- operatorNameMap[proposedName] = newName; |
+ operatorNameMap[operatorIdentifier] = newName; |
} |
return newName; |
} |
+ /// Returns an unused name. |
+ /// |
+ /// [proposedName] must be a valid JavaScript identifier. |
+ /// |
+ /// If [ensureSafe] is `false`, then [proposedName] must not be a keyword. |
+ /// |
+ /// If [sanitizeForAnnotations] is `true`, then the result is guaranteed not |
+ /// to have the form of an annotated name. |
+ /// |
+ /// Note that [MinifyNamer] overrides this method with one that produces |
+ /// minified names. |
String getFreshName(String proposedName, |
Set<String> usedNames, |
Map<String, String> suggestedNames, |
- {bool ensureSafe: true}) { |
- var candidate; |
+ {bool ensureSafe: true, |
+ bool sanitizeForAnnotations: false}) { |
+ if (sanitizeForAnnotations) { |
+ proposedName = _sanitizeForAnnotations(proposedName); |
+ } |
if (ensureSafe) { |
proposedName = safeName(proposedName); |
} |
assert(!jsReserved.contains(proposedName)); |
+ String candidate; |
if (!usedNames.contains(proposedName)) { |
candidate = proposedName; |
} else { |
- var counter = popularNameCounters[proposedName]; |
- var i = counter == null ? 0 : counter; |
+ int counter = popularNameCounters[proposedName]; |
+ int i = counter == null ? 0 : counter; |
floitsch
2015/01/30 21:03:36
(counter == null)
asgerf
2015/02/03 17:39:11
Done.
|
while (usedNames.contains("$proposedName$i")) { |
i++; |
} |
@@ -615,15 +802,46 @@ class Namer implements ClosureNamer { |
return candidate; |
} |
+ /// Returns a variant of [name] that cannot clash with the annotated |
+ /// version of another name, that is, the resulting name can never be returned |
+ /// by [deriveGetterName], [deriveSetterName], [deriveMethodName], |
+ /// [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`. |
floitsch
2015/01/30 21:03:37
Add '_isAnnotated' method and assert that every an
asgerf
2015/02/03 17:39:13
I don't think it's worth it anymore, since it woul
|
+ /// |
+ /// 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 contain a $ followed by a digit, since this could |
+ // clash with the suffix we append on method names. |
+ for (int i = name.indexOf(r'$'); i != -1; i = name.indexOf(r'$', i+1)) { |
+ if (i + 1 < name.length && isDigit(name.codeUnitAt(i+1))) { |
+ // Strip off everything after the $ to be safe. |
+ name = name.substring(0, i + 1); |
+ break; |
+ } |
+ } |
+ // Ensure name does not clash with a getter or setter of another name, or |
+ // one of the other special names that start with `$`, such as `$is`. |
+ if (name.startsWith(r'$') || |
+ name.startsWith(getterPrefix) || |
+ name.startsWith(setterPrefix)) { |
+ name = '\$$name'; |
+ } |
+ return name; |
+ } |
+ |
String getClosureVariableName(String name, int id) { |
return "${name}_$id"; |
} |
/** |
- * Returns a preferred JS-id for the given top-level or static element. |
+ * Returns a proposed name for the given top-level or static element. |
* The returned id is guaranteed to be a valid JS-id. |
*/ |
- String _computeGuess(Element element) { |
+ String _proposeNameForGlobal(Element element) { |
assert(!element.isInstanceMember); |
String name; |
if (element.isGenerativeConstructor) { |
@@ -695,17 +913,24 @@ class Namer implements ClosureNamer { |
return names.join(); |
} |
- String getInterceptorName(Element element, Iterable<ClassElement> classes) { |
+ /// Property name used for `getInterceptor` or one of its specializations. |
floitsch
2015/01/30 21:03:37
as written in another file:
"nameOfGetInterceptorF
asgerf
2015/02/03 17:39:11
Done.
|
+ String getInterceptorName(Iterable<ClassElement> classes) { |
+ FunctionElement getInterceptor = backend.getInterceptorMethod; |
if (classes.contains(backend.jsInterceptorClass)) { |
// If the base Interceptor class is in the set of intercepted classes, we |
// need to go through the generic getInterceptorMethod, since any subclass |
// of the base Interceptor could match. |
- return getNameOfInstanceMember(element); |
+ // The unspecialized getInterceptor method can also be accessed through |
+ // its element, so we treat this as a user-space global instead of an |
+ // intern global. |
+ return disambiguateGlobal(getInterceptor); |
} |
String suffix = getInterceptorSuffix(classes); |
- return getMappedGlobalName("${element.name}\$$suffix"); |
+ return disambiguateInternGlobal("${getInterceptor.name}\$$suffix"); |
} |
+ /// Property name used for the one-shot interceptor method for the given |
+ /// [selector] and return-type specialization. |
String getOneShotInterceptorName(Selector selector, |
Iterable<ClassElement> classes) { |
// The one-shot name is a global name derived from the invocation name. To |
@@ -718,113 +943,67 @@ class Namer implements ClosureNamer { |
// If the base Interceptor class is in the set of intercepted classes, |
// this is the most general specialization which uses the generic |
// getInterceptor method. To keep the name short, we add '$' only to |
- // distinguish from global getters or setters; operators and methods can't |
- // clash. |
+ // distinguish from other intern globals that don't contain '$' symbols. |
// TODO(sra): Find a way to get the simple name when Object is not in the |
// set of classes for most general variant, e.g. "$lt$n" could be "$lt". |
if (selector.isGetter || selector.isSetter) root = '$root\$'; |
floitsch
2015/01/30 21:03:37
Why does a getter and setter need the "$" ?
The ol
asgerf
2015/02/03 17:39:12
Rephrased comment.
|
- return getMappedGlobalName(root, ensureSafe: false); |
+ return disambiguateInternGlobal(root, ensureSafe: false); |
} else { |
String suffix = getInterceptorSuffix(classes); |
- return getMappedGlobalName("$root\$$suffix", ensureSafe: false); |
+ return disambiguateInternGlobal("$root\$$suffix", ensureSafe: false); |
} |
} |
- /// Returns the runtime name for [element]. The result is not safe as an id. |
- String getRuntimeTypeName(Element element) { |
+ /// 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. |
floitsch
2015/01/30 21:03:37
Yeah for weird arbitrary rules...
asgerf
2015/02/03 17:39:12
Still better than undocumented arbitrary rules...
|
+ String getRuntimeTypeName(TypeDeclarationElement element) { |
if (element == null) return 'dynamic'; |
- return getNameForRti(element); |
- } |
- |
- /** |
- * Returns a preferred JS-id for the given element. The returned id is |
- * guaranteed to be a valid JS-id. Globals and static fields are furthermore |
- * guaranteed to be unique. |
- * |
- * For accessing statics consider calling [elementAccess] instead. |
- */ |
- // TODO(ahe): This is an internal method to the Namer (and its subclasses) |
- // and should not be call from outside. |
- String getNameX(Element element) { |
- if (element.isInstanceMember) { |
- if (element.kind == ElementKind.GENERATIVE_CONSTRUCTOR_BODY |
- || element.kind == ElementKind.FUNCTION) { |
- return instanceMethodName(element); |
- } else if (element.kind == ElementKind.GETTER) { |
- return getterName(element); |
- } else if (element.kind == ElementKind.SETTER) { |
- return setterName(element); |
- } else if (element.kind == ElementKind.FIELD) { |
- compiler.internalError(element, |
- 'Use instanceFieldPropertyName or instanceFieldAccessorName.'); |
- return null; |
- } else { |
- compiler.internalError(element, |
- 'getName for bad kind: ${element.kind}.'); |
- return null; |
- } |
- } else { |
- // Use declaration element to ensure invariant on [globals]. |
- element = element.declaration; |
- // Dealing with a top-level or static element. |
- String cached = globals[element]; |
- if (cached != null) return cached; |
- |
- String guess = _computeGuess(element); |
- ElementKind kind = element.kind; |
- if (kind == ElementKind.VARIABLE || |
- kind == ElementKind.PARAMETER) { |
- // The name is not guaranteed to be unique. |
- return safeName(guess); |
- } |
- if (kind == ElementKind.GENERATIVE_CONSTRUCTOR || |
- kind == ElementKind.FUNCTION || |
- kind == ElementKind.CLASS || |
- kind == ElementKind.FIELD || |
- kind == ElementKind.GETTER || |
- kind == ElementKind.SETTER || |
- kind == ElementKind.TYPEDEF || |
- kind == ElementKind.LIBRARY) { |
- bool fixedName = false; |
- if (Elements.isInstanceField(element)) { |
- fixedName = element.hasFixedBackendName; |
- } |
- String result = fixedName |
- ? guess |
- : getFreshName(guess, usedGlobalNames, suggestedGlobalNames, |
- ensureSafe: true); |
- globals[element] = result; |
- return result; |
- } |
- compiler.internalError(element, |
- 'getName for unknown kind: ${element.kind}.'); |
- return null; |
- } |
- } |
- |
- String getNameForRti(Element element) => getNameX(element); |
- |
- String getNameOfLibrary(LibraryElement library) => getNameX(library); |
- |
- String getNameOfClass(ClassElement cls) => getNameX(cls); |
- |
- String getNameOfField(VariableElement field) => getNameX(field); |
- |
- // TODO(ahe): Remove this method. Use get getNameOfMember instead. |
- String getNameOfInstanceMember(Element member) => getNameX(member); |
- |
+ // 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 a '$' character. |
+ return disambiguateGlobal(element); |
+ } |
+ |
+ /// Returns the disambiguated name of [class_]. |
+ /// |
+ /// This is both the *runtime type* of the class (see [getRuntimeTypeName]) |
+ /// and a global property name in which to store its JS constructor. |
+ String getNameOfClass(ClassElement class_) => disambiguateGlobal(class_); |
+ |
+ /// Property name on which [member] can be accessed directly, |
+ /// without clashing with another JS property name. |
floitsch
2015/01/30 21:03:36
Might help to give a small example when this is ne
asgerf
2015/02/03 17:39:13
Done, although I don't understand your example, so
floitsch
2015/02/04 00:10:11
Interesting. I didn't know that we added the alias
|
String getNameOfAliasedSuperMember(Element member) { |
- ClassElement superClass = member.enclosingClass; |
- String className = getNameOfClass(superClass); |
- String memberName = getNameOfMember(member); |
- String proposal = "$superPrefix$className\$$memberName"; |
- // TODO(herhut): Use field naming constraints (unique wrt. inheritance). |
- return getMappedInstanceName(proposal); |
+ assert(!member.isField); // Fields do not need super aliases. |
+ return disambiguateDirectAccess(member, |
+ () => '\$super\$${member.enclosingClass.name}\$${member.name}'); |
} |
- String getNameOfMember(Element member) => getNameX(member); |
- |
- String getNameOfGlobalField(VariableElement field) => getNameX(field); |
+ /// 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. |
floitsch
2015/01/30 21:03:37
But is it unique within their respective categorie
asgerf
2015/02/03 17:39:12
Ideally, the users of Namer shouldn't have to know
|
+ String getNameOfMethod(Element method) { |
floitsch
2015/01/30 21:03:38
Why is this prefixed with "get"?
Most of the othe
asgerf
2015/02/03 17:39:13
I agree. I've changed it to "methodName".
|
+ return method.isInstanceMember |
+ ? instanceMethodName(method) |
+ : globalPropertyName(method); |
+ } |
/// Returns true if [element] is stored on current isolate ('$'). We intend |
/// to store only mutable static state in [currentIsolate], constants are |
@@ -861,19 +1040,19 @@ class Namer implements ClosureNamer { |
String getLazyInitializerName(Element element) { |
assert(Elements.isStaticOrTopLevelField(element)); |
- return getMappedGlobalName("$getterPrefix${getNameX(element)}"); |
+ return disambiguateInternGlobal("$getterPrefix${globalPropertyName(element)}"); |
floitsch
2015/01/30 21:03:36
long line.
asgerf
2015/02/03 17:39:12
Done.
|
} |
String getStaticClosureName(Element element) { |
assert(Elements.isStaticOrTopLevelFunction(element)); |
- return getMappedGlobalName("${getNameX(element)}\$closure"); |
+ return disambiguateInternGlobal("${globalPropertyName(element)}\$closure"); |
} |
// This name is used as part of the name of a TypeConstant |
String uniqueNameForTypeConstantElement(Element element) { |
// TODO(sra): If we replace the period with an identifier character, |
// TypeConstants will have better names in unminified code. |
- return "${globalObjectFor(element)}.${getNameX(element)}"; |
+ return "${globalObjectFor(element)}.${globalPropertyName(element)}"; |
} |
String globalObjectForConstant(ConstantValue constant) => 'C'; |
@@ -919,7 +1098,7 @@ class Namer implements ClosureNamer { |
return operatorIs(type.element); |
} |
- String operatorIs(Element element) { |
+ String operatorIs(ClassElement element) { |
// TODO(erikcorry): Reduce from $isx to ix when we are minifying. |
return '${operatorIsPrefix}${getRuntimeTypeName(element)}'; |
} |
@@ -929,7 +1108,7 @@ class Namer implements ClosureNamer { |
* and also ensures it won't clash with other identifiers. |
floitsch
2015/01/30 21:03:38
with other reserved identifiers (like "__proto__")
asgerf
2015/02/03 17:39:13
Actually I think that was a stray comment from whe
|
*/ |
String _safeName(String name, Set<String> reserved) { |
- if (reserved.contains(name) || name.startsWith(r'$')) { |
+ if (reserved.contains(name)) { |
name = '\$$name'; |
} |
assert(!reserved.contains(name)); |
@@ -940,7 +1119,7 @@ class Namer implements ClosureNamer { |
// TODO(ahe): Creating a string here is unfortunate. It is slow (due to |
floitsch
2015/01/30 21:03:38
I don't know what Peter means.
The cost in string
asgerf
2015/02/03 17:39:12
Done.
|
// string concatenation in the implementation), and may prevent |
// segmentation of '$'. |
- return '${operatorAsPrefix}${getNameForRti(element)}'; |
+ return '${operatorAsPrefix}${getRuntimeTypeName(element)}'; |
} |
String safeName(String name) => _safeName(name, jsReserved); |