JavaScript 2.0
Core Language
Definitions
previousupnext

Thursday, May 22, 2003

Introduction

Definitions are directives that introduce new constants, variables, functions, classes, namespaces, and packages. All definitions except those of packages can be preceded by zero or more attributes. In non-strict mode there must not be any line breaks between the attributes or after the last attribute.

Attributes

Attributes 
   Attribute
|  AttributeCombination
AttributeCombination  Attribute [no line break] Attributes
Attribute 
   AttributeExpression
|  true
|  false
|  ReservedNamespace

An attribute is an expression (usually just an identifier) that modifies a definition’s meaning. Attributes can specify a definition’s scope, namespace, semantics, and other hints. A JavaScript program may also define and subsequently use its own attributes. Attributes can be qualified identifiers (as long as they don’t start with a () and dotted and function call expressions, but they must be compile-time constants.

The table below summarizes the predefined attributes.

Category Attributes Behavior
Namespace private
internal
public
Makes the definition visible only in the enclosing class’s private namespace (private), the enclosing package’s private namespace (internal), or anywhere (public).
Visibility Modifier enumerable This definition can be seen using a for-in statement.
explicit This top-level definition is not shared via an import directive.
Class Modifier final This class cannot be subclassed. Can be used only on classes.
dynamic Direct instances of this class can contain dynamic properties. Can be used only on classes.
Member Modifier   static
virtual
final
The definition creates a global member (static) or instance member (virtual or final) of the enclosing class. If defining an instance member, the definition can (virtual) or cannot (final) be overridden in subclasses. Can be used only on class members.
override
override(true)
override(false)
override(undefined) 
Assertion that the definition overrides (override or override(true)), may override (override(undefined)), or does not override (override(false)) a member of a superclass. Can be used only on class members. Controls errors only.
Conditional true
false
The definition or directive is (true) or is not (false) processed.
Memory Management weak Makes the defined variable be a weak reference. Can be used only on var definitions.
Miscellaneous prototype Allows a function to access this and be used as a prototype-based constructor.
unused Assertion that the definition is not used.

Multiple conflicting attributes cannot be used in the same definition, so virtual final private is an error. The attributes true and false do not conflict. Specifying an attribute more than once has the same effect as specifying it once.

Namespace Attributes

Namespace attributes control the definition’s visibility. User-defined attributes provide a finer grain of visibility control.

Every package P has a predefined, anonymous namespace PackageInternalP. That namespace is attached to all definitions with the internal attribute in that package. Package P’s scope includes an implicit use namespace(PackageInternalP) definition around the package that grants access to these definitions from within the package only.

Every class C has a predefined, anonymous namespace ClassInternalC. That namespace is attached to all definitions with the private attribute in that class. Class C’s scope includes an implicit use namespace(ClassInternalC) definition around the class that grants access to these definitions from within that class only. private can only be used inside a class.

Namespace attributes, including user-defined namespaces, are additive; if several are given for a definition, then that definition is put into each of the designated namespaces. Thus, a single definition may define a name in two or more namespaces namespace1 and namespace2 by listing the namespaces as attributes: namespace1 namespace2 var x. Such multiple definitions are aliases of each other; there is only one storage location x.

A definition of a name id is always put into the namespaces explicitly specified in the definition’s attributes. In addition, the definition may be placed in additional namespaces according to the rules below:

Visibility Modifier Attributes

Visibility modifier attributes control the definition’s visibility in several special cases.

enumerable

An enumerable definition can be seen by the for-in iteration statement. A non-enumerable definition cannot be seen by such a statement. enumerable only applies to public definitions.

The default for dynamic properties and class properties is enumerable. The default for instance properties is non-enumerable. There is no way to make a user-defined dynamic or class property non-enumerable.

explicit

explicit is used to add definitions to a package P without having them conflict with definitions in other packages that import package P. explicit prevents the definition from being accessed as a top-level variable when a package is imported. The definition can still be accessed as a property of an object. For example,

package My.P1 {
  const c1 = 5;
  explicit const c2 = 7;
}

package My.P2 {
  import P = My.P1;  // Imports My.P1 without qualification
  c1;                // OK; evaluates to 5
  c2;                // Error: c2 not defined because explicit variables are not shared
  P.c2;              // OK: explicit properties are visible
}

Class Modifier Attributes

Class modifier attributes apply to the definition of a class C itself. They may only be used on definitions of classes.

final

If a class C is defined using the final attribute, then any attempt to define a subclass of C signals an error.

Note that final is also a member modifier — when used on a class member, final makes that member nonoverridable. If final is used on a class member that is itself a class, then it acts like a class modifier instead of a member modifier — it prevents the inner class from being subclassed.

dynamic

Direct instances of a dynamic class C can contain dynamic properties. Other instances cannot contain dynamic properties. A class is dynamic if it has the dynamic attribute or it has a dynamic ancestor other than Object.

Member Modifier Attributes

Member modifier attributes modify a class member definition’s semantics with respect to a class hierarchy. They may only be used on a definition of a member M of a class C. They cannot be used on definitions that, for example, create local variables inside a function.

static, virtual, and final

The static attribute makes M be a global member of C.

The virtual and final attributes make M be an instance member of C.

The final attribute prevents subclasses from defining their own members with the name M (unless they can’t see this M, in which case they can define an independent M). virtual allows subclasses to override M.

The default setting for the definition of a member M of a class named C is:

Default Attribute Kind of Member M
none — the function is treated specially as a class constructor   function C
virtual function F where the name F differs from C
final var and const definitions
none — static attribute must be specified explicitly class and namespace definitions

These attributes may not be used on an export definition, since export reuses the original member’s setting.

Note that final is also a class modifier — when used on a class member M, final prevents M from being subclassed rather than making M be a nonoverridable instance member of C.

override

The override attribute reports errors; it has no other effect on the behavior of the program. The override attribute can only be used on definitions in a class and describes the programmer’s intent to either override or not override a member from a superclass. If the actual behavior, as defined by the namespace defaulting and overriding rules, differs, then an error is signaled.

The table below describes the behavior of when a definition of a member M with name id is placed in a class C:

Override attribute given
None override or
override(true)
override(undefined) override(false)
M overrides a member in some superclass according to the namespace defaulting and overriding rules Error OK OK Error
M does not override anything but there exists an ancestor of C with a member with name id visible at the point of definition of M Error Error OK OK
M does not override anything and no ancestor of C has a member with name id visible at the point of definition of M OK Error OK OK

The middle case arises for example when an ancestor of a class C defines a public member named X and class C attempts to define a private member named X.

Conditional Attributes

An attribute whose value is true causes the definition or directive to be evaluated normally. An attribute whose value is false causes the definition or directive to be skipped; the remaining attributes and the body of the definition or directive are not evaluated. These are useful for turning definitions on and off based on configuration settings, such as:

const debug = true;
const nondebug = !debug;

debug var nCalls = 0;
debug function checkConsistency() {...}

Memory Management Attributes

weak

The weak attribute, which can be used only on var definitions, makes the defined variable be a weak reference. If there is no way to reference the variable’s value except via weak references, then the system may, at its option and at any time, replace the variable’s value with null. The variable’s type must allow null.

Miscellaneous Attributes

prototype

The prototype attribute can only be used on a function. A function with this attribute treats this in the same manner as JavaScript 1.5 and defines its own prototype-based class as in JavaScript 1.5. By default, the prototype attribute is set on any unchecked function. It can be set explicitly on other functions as long as they are not getters, setters, or constructors.

unused

The unused attribute is a hint that the definition is not referenced anywhere. Referencing the definition will generate an error.

User-Defined Attributes

A user-defined attribute may be defined using a const definition or other definitions that define constants. All attributes must be compile-time constants. For example:

const ipriv = internal static;
explicit namespace Version1;
explicit namespace Version2;
internal const Version1and2 = Version1 Version2;

class C {
  ipriv var x;                          // Same as internal static var x;
  Version1and2 var simple;              // Same as Version1 Version2 var simple;
  Version2 var complicated;
  ipriv const a:Array = new Array(10);

  private var i;
  for (i = 0; i != 10; i++) a[i] = i;
}

Definition Scope

A definition applies to the innermost enclosing scope except when it is hoisted. If that scope is a class, the definition appears as a member of that class. If that scope is a package, the definition appears as a member of that package.

Scope Hoisting

For compatibility with JavaScript 1.5, in some cases a definition’s scope is hoisted to the innermost regional scope R instead of the innermost scope S. This happens only when all of the conditions below are met:

When a definition of n is hosted, the effect is as though n were declared (but not initialized) at the top of the regional scope R.

Definitions not meeting the above criteria are not hoisted. However, an inner non-hoisted definition of name n in scope S within regional scope R prevents n from being referenced or defined in any scope within R but outside S; see definition conflicts.

Extent

A definition extends an activation frame with one or more bindings of qualified names to values. The bindings are generally visible from the activation frame’s scope. However, a definition may be invisible or partially invisible inside its scope either because it is shadowed by a more local definition or it uses a namespace that is not used. The name lookup rules specify the detailed behavior of accessing activation frame bindings.

Each definition or declaration D of a name n applies to some scope S using the rules above. Any of S’s activation frames will contain a binding for n as soon as S is entered. That binding starts in the following state:

Accessing an activation frame binding in the uninitialized state is an error. If this happens, implementations are encouraged to throw an exception, but may return a value V if they can prove that the definition would assign the value V to the binding.

Definition Conflicts

In general, it is not legal to rebind the same name in the same namespace within an activation frame A. There are a couple exceptions:

In addition, if a name n is defined in a scope S inside regional scope R, then it is not permitted to access a definition of n made outside of R from anywhere inside R. Also, two nested scopes S1 and S2 located inside the same regional scope R cannot both define n (S1, S2, and R may be the same scope). In either of these situations, n may be hoisted; if hoisting is not allowed, an error occurs. For example,

const b:Integer = 1;

function f(c:Boolean):Integer {
  const a = b;  // Error: b is defined inside the local scope below, which prevents accesses to global b
                // from anywhere inside the regional scope
  if (c) {
    const b:Integer = a + 10;  // OK to hide the global b from here.
    return b;
  }
  return a;
}

function g(c:Boolean):Integer {
  const b = 3;  // OK to hide the global b from here.
  if (c) {
    const b:Integer = 10;  // Error: can’t redefine b inside the same regional scope.
    return b;
  }
  return b;
}

function h(c:Boolean):Integer {
  if (c) {
    const b:Integer = 10;  // OK to hide the global b from here.
    return b;
  } else {
    const b:Integer = 42;  // OK: Two independent local definitions of b.
    return b;
  }
}

To help catch accidental redefinitions, binding a qualified name q::n in activation frame A when there is already a binding r::n in A causes an error if both namespaces q and r are used at the point of the definition of q::n and the bindings are not aliases of each other. This prevents the same name from being used for both public and private variables in the same class. Two bindings sharing the same name but with different namespaces may still be introduced into an activation frame, but only by code that does not use one or both of the namespaces.

Examples

In the example below the comments indicate the scope and namespace of each definition:

var a0;                  // Public global variable
internal const a1 = true;// Package-visible global variable
private var a2;          // Error: private  can only be used inside a class
public var a3 = b1;      // Public global variable

if (a1) {
  var b0;                // Local to this block
  var b1;                // Hoisted to the global level because of the reference to b1 in the definition of a3
}

if (a1) {
  var b0;                // Local to this block
}

public function F() {    // Public global function
  var c0;                // Local to this function
  internal var c1;       // Local to this function  (may generate a style warning)
  public var c2;         // Local to this function  (may generate a style warning)
}

class C {                // Public global class
  var e0;                // Public class instance variable
  private var e1;        // Class-visible class instance variable
  internal var e2;       // Package-visible class instance variable
  public var e3;         // Public class instance variable
  static var e4;         // Public class-global variable
  private static var e5; // Class-visible class-global variable
  internal static var e6;// Package-visible class-global variable
  public static var e7;  // Public class-global variable

  if (a1) {
    var f0;              // Local to this block
    private var f1;      // Local to this block  (may generate a style warning)
  }
  public function I() {} // Public class method
}

Definition Renaming

Sometimes it is useful to add a namespace to a prior definition of a name (perhaps inherited from a superclass) or rename a name inherited from a superclass or imported package. The export definition provides a facility to make an alias of an existing name:

ExportDefinition  export ExportBindingList
ExportBindingList 
ExportBinding 

The first FunctionName is the new name. If the second FunctionName is provided then it is the existing name; otherwise, the first FunctionName is also the existing name. If one of the FunctionNames is a getter or setter, then the other one must be a getter or setter, respectively.

A new name is an exact alias of the old name rather than a separate value or entity. Thus, if method A is renamed as method B and B is subsequently overridden by B', then B' really overrides A.

Here are a few examples of definition renaming:

package Light {
  const red = 1;
  const green = 2;
  const blue = 4;
  const white = red + green + blue;
}

package Saber {
  import Light;

  namespace New;
  namespace Improved;

  export white; // Re-export Light’s white
  New export green; // Re-export Light’s green, but only in the new version

  class C {
    internal var size;
    function color() {}
  }

  class D extends C implements I1, I2 {
    var inertia;
    export size; // Make size public in instances of D
    export colour = color; // Make alias for British users

    function mispelled() {}; // Oops! Saber was already released and it’s too late to remove this
    Improved export misspelled = mispelled; // Users of the improved version can use the good name
  }
}

Discussion

Should we have a protected Attribute? It has been omitted for now to keep the language simple, but there does not appear to be any fundamental reason why it could not be supported. If we do support it, it might be better to choose the C++ protected concept (visible only in class and subclasses); the Java protected concept (visible in class, subclasses, and the original class’s package) could be represented as internal protected.


Waldemar Horwat
Last modified Thursday, May 22, 2003
previousupnext