Proposal by Allen Wirfs-Brock
An alternative to the revised private_names proposal by Allen Wirfs-Brock
and the original names proposal by Dave Herman and Sam Tobin-Hochstadt .
In existing ECMAScript, string values are distinguished solely based upon their specific ordered sequence of characters. Two strings that have the same sequence of characters are considered to be the same value, even though they originate at different points in a program or exist as distinct implementation level entities. Unique strings extends the ECMAScript string type to also include the concept of distinct strings values that may have identical character sequences.
These values are similar, in concept, to the unique symbols generated by the gensym function in Lisp and other similar languages. They are useful for creating unique property names that are guaranteed to be different from any other property name that has or in the future may be created.
Section 8.4 of the ECMAScript specification defines a string to be finite ordered sequence of 16-bit unsigned integer character values. That definition is extended as follows:
Each string value contains a immutable Boolean value named [[Secret]] and an immutable integer value named [[UniqueId]]. Just like a string’s character sequence, the values of [[Secret]] and [[UniqueId]] are set during the creation of a string value and may not be subsequently modified. Unless other specified, each string value is created with false as its [[Secret]] and 0 as its [[UniqueId]]. In particular, the SV of string literals (7.8.4) have the default values for [[Secret]] and [[UniqueId]]. In the rest of the specification the term Unique String is used to mean a string values whose [[UniqueId]] value is non-zero. The term Private String is used to mean a string values whose [[UniqueId]] value is non-zero and whose ‘[[Secret]] value is true. String values are never created with both a [[UniqueId]] value of zero and a ‘[[Secret]] value that is true.
Note [[Secret]] and [[UniqueId]] are specification devices. They need not literally exist within an implementation as long as the observable behavior of the implementation matches the observable characteristics of this specification.
This extension does not change the semantics of any ECMAScript operations upon string values other than those explicitly identified in this specification. The values of [[Secret]] and [[UniqueId]] have no effect upon such string operations unless explicitly identified by this specification as having an effect.
In some cases the current ECMAScript specification is imprecise concerning how a string value is used as a name for accessing or creating an object property. The following clarification are made as part of this proposal:
Replace:
1. If O doesn’t have an own property with name P, return undefined.
with:
1. If O dosn’t have an own property whose name is the SameValue as P, return undefined.
Replace:
3. Let X be O‘s own property named P.
with:
3. Let X be O‘s own property whose name is the SameValue as P.
Replace:
3.a. Remove the own property with name P from O.
with:
3.a. Remove the own property whose name is the SameValue as P from O.
Replace:
12. For each attribute field of Desc that is present, set the corresponding named attribute of the property named P of object O to the value of the field.
with:
12. For each attribute field of Desc that is present, set the corresponding named attribute of the property of object O whose name is the SameValue as P to the value of the field.
Note that this proposal does not modify the Abstract Relational Comparison Algorithm or the Abstract Equality Comparison Algorithm.
Replace:
5. If Type(x) is String, then return true if x and y are exactly the same sequence of characters (same length and same characters in corresponding positions); otherwise return false.
with:
5. If Type(x) is String, then return true if x and y have the same [[UniqueId]] value and have exactly the same sequence of characters (same length and same characters in corresponding positions); otherwise return false.
Note that if the proposed egal operator should use this modified specification of the SameValue Algorithm.
Replace:
5. If Type(x) is String, then return true if x and y are exactly the same sequence of characters (same length and same characters in corresponding positions); otherwise return false.
with:
5. If Type(x) is String, then return true if x and y have the same [[UniqueId]] value and have exactly the same sequence of characters (same length and same characters in corresponding positions); otherwise return false.
String values with non-zero [[UniqueId]] values and possibly a true [[Secret]] are created using the following new built-in property of the String Constructor. These are the only way that new Unique String values can be created. It is impossible to synthesis an already existing Unique String value using a String literal or in any other manner.
If value is not present let value be the empty string. If the type of value is not String let value be the result of ToString(value). Returns a String value containing the same sequence of characters as value. The value of [[UniqueId]] for the returned string is set to an integer that is 1 greater than any previous value that has been assigned to any string’s [[UniqueId]]. The value of [[Secret]] for the returned string is set to false.
This function most closely corresponds to the Lisp gensym function. The value is provided primarily for debugging purposes and can be omitted.
If value is not present let value be the empty string. If the type of value is not String let value be the result of ToString(value). Returns a String value containing the same sequence of characters as value. The value of [[UniqueId]] for the returned string is set to an integer that is 1 greater than any previous value that has been assigned to any string’s [[UniqueId]]. The value of [[Secret]] for the returned string is set to true.
String.secret operates exactly like String.unique except that the [[Secret]] internal property of returned string is set to true. The [[Secret]] internal property is used by reflective operations that expose property names to limit the exposure of such names. Code can only directly gain access via reflection to a secret string valued property name if it already has access to that secret string value.
Because unique string values are real string values, they may be used in any context where a string is required including as property names. However, such properties can only be created and accessed using bracket notation or reflection operations. For example, a property with a unique string name might be added to an object as follows:
var x = String.unique(); //create a new Unique Name value. var obj = {}; obj[x] = 1; print(obj[x]); //prints 1
Because the SameValue algorithm is used for property lookup distinct unique string values designate separate properties even when the unique strings have the same character sequences:
var x = String.unique("x"); //create a new Unique String value. var x2 = String.unique("x"); //create another Unique String value. var obj = {}; obj[x] = 1; obj[x2] = 2 print(obj[x]+obj[x2]); //prints 3
Property named using unique strings are also distinct from properties accessed using normal strings or dot notation:
var x = String.unique("x"); //create a new Unique Name value. var obj = {}; obj[x] = 1; print(obj["x"]); //prints undefined print(obj.x); //prints undefined
The use of an unique string (even those with a true value for [[Secret]]) has no direct effect on the enumerablity of properties. If it is desired that a unique name not be exposed via for-in enumeration then its property must be explicitly made non-enumerable:
var y = String.secret(); //create a new Unique strng value. var obj = {a:1}; Object.defineProperty(obj,y,{value: 2, enumerable: false}} for (var p in obj) print(p); //prints "a"
Setting [[Secret]] to true for a unique string prevents that string from being directly exposed using certain reflective operations. Instead, in those situations, a special secrecy wrapper object is used instead of a unique string value.
A Secrecy Wrapper is an object with an [[Hidden]] internal property and the following two methods. Secret Wrapper objects are always created as frozen objects.
If either the this object or the value of obj does not have a [[Hidden]] internal property return false. Otherwise, return the result of applying the SameValue algorithm to the values of the [[Hidden]] internal property of the this object and obj.
If the this object does not have a [[Hidden]] internal property throw TypeError exception. Otherwise, return the result of applying the SameValue algorithm to the value of the [[Hidden]] internal properties of the this object and the value of obj.
Secrecy wrapper are used instead of string values in the following situations:
[[Secret]] value is true that would be contained in the array returned by Object.getOwnPropertyNames.[[Secret]] value is true.Unique Strings are explicitly created and existing or previously existing Unique Strings can not be re-synthesized. Unlike other string values they are subject to garbage collection. Because of this, it is useful to use them as keys of WeakMaps. The weak maps proposal is extended to allow Unique Strings to be used as WeakMap keys.