April 2002 Draft
JavaScript 2.0
Core Language
Definitions
previousupnext

Friday, April 12, 2002

Introduction

Definitions are directives that introduce new constants, variables, functions, classes, interfaces, namespaces, and packages. All definitions except those of packages can be preceded by zero or more attributes. 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
|  public
|  NonexpressionAttribute
NonexpressionAttribute 
   abstract
|  final
|  private
|  static

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
Scope extend(C) The definition extends class C.
unit Shorthand for extend(Unit).
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 accessed using the [] operator and seen using a for-in statement.
explicit This top-level definition is not shared via an import directive.
Evaluation Modifier compile This definition defines a compile-time constant.
Class Modifier final This class cannot be subclassed. Can be used only on classes.
dynamic Instances of this class can contain dynamic properties. Can be used only on classes.
Member Modifier   static
constructor
operator
abstract
virtual
final
The definition creates a global member (static), constructor (constructor), operator override (operator), or instance member (abstract, virtual, or final) of the enclosing class or interface. If defining an instance member, the definition must (abstract), can (virtual), or cannot (final) be overridden in subclasses. Can be used only on class or interface 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 or interface 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. If the function is an instance member of a class, then the function is visible via the class’s prototype global member and can take any object in the this parameter.
unused Assertion that the definition is not used.

Multiple conflicting attributes cannot be used in the same definition, nor can an identical attribute be repeated. Thus, virtual final private and public public are both errors.

Scope Attribute

A scope attribute describes the scope to which a definition applies. If no scope attribute is given, the definition applies to the innermost enclosing scope except when it is hoisted. If that scope is a class or interface, the definition appears as a member of that class or interface. 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.

Class Extensions

The extend attribute takes a parameter C, which should be a compile-time constant expression that evaluates to a class, and adds the definition as a new member of class C. This allows one to add a method to an existing class C even if C is in an already defined package P. There are several restrictions:

The following example indicates adding methods to the system class String, using a newly created namespace StringExtension:

namespace StringExtension;

StringExtension extend(String) function scramble():String {...}
StringExtension extend(String) function unscramble():String {...}

use namespace(StringExtension);

var x:String = "abc".scramble();

Once the class extension is evaluated, methods scramble and unscramble become available on all strings in code within the scope of a use namespace(StringExtension). There is no possibility of name clashes with extensions of class String in other, unrelated packages because the names scramble and unscramble only acquire their special meanings when qualified by the namespace StringExtension.

Unless one desires to export a class extension to other packages, the default namespace internal generally works best and simplifies the above example to:

extend(String) {
  function scramble():String {...}
  function unscramble():String {...}
}

var x:String = "abc".scramble();

Unit Class Extension

The unit attribute is defined as though

compile const unit = extend(Unit);

were evaluated at the top level. Unit is the class that holds the definitions of unit names.

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:

Definitions of top-level entities in a package can only be placed in the namespace public or in namespaces (including internal) defined within that package. See rationale.

Visibility Modifier Attributes

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

Indexable

An indexable definition can be accessed using the default [] operator. A nonindexable definition cannot be accessed using this operator. Non-public definitions cannot be indexable.

The default for fixed properties is nonindexable and for dynamic properties is indexable. One can make a fixed property indexable by making it enumerable. There is no way to make a user-defined fixed property indexable but not enumerable. There is also no way to make a dynamic property nonindexable.

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 implies indexable.

The default for fixed properties is non-enumerable and for dynamic properties is enumerable. There is no way to make a user-defined dynamic 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
}

Evaluation Attribute

compile

If the compile attribute is given on a const definition of C, then C’s value must be a compile-time constant, and C itself will be considered to be a compile-time constant usable in other compile-time expressions. When the const definition is a class member, the compile attribute implies static.

The compile attribute is implicit in class, interface, and namespace definitions, but may also be specified explicitly on these. The compile attribute cannot be given on var or function definitions.

A const definition with the compile attribute can be accessed by non-compile-time expressions throughout its scope, including forward-referenced ones. On the other hand, the expression defining the value of a compile const definition cannot contain forward references.

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

Instances of a dynamic class C or its subclasses can contain dynamic properties. Other instances cannot contain dynamic properties.

Member Modifier Attributes

Member modifier attributes modify a class or interface 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 or interface C. They cannot be used on definitions that, for example, create local variables inside a function.

static, constructor, operator, abstract, virtual, and final

The static (or compile, which implies static) attribute makes M be a global member of C.

The constructor attribute makes M be a global member of C that constructs new instances of C.

The operator attribute makes M be an operator override. M must be a function definition using one of the special quoted operator names and signatures.

The abstract, 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 and abstract allow subclasses to override M. In addition, abstract causes an error to be signaled if an attempt is made to get or set the value of this class’s M. Note that, if M is abstract, a subclass does not have to implement M, but if it doesn’t then it can’t access it.

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

Default Attribute Kind of Member M
constructor    function C
abstract    function F without a body, where the name F differs from C
virtual function F with a body, where the name F differs from C
final var and const definitions
static class, interface, 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:

compile const debug = true;
compile 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, which can only be used on a function definition, imposes JavaScript 1.5-like prototype semantics on that function. If such a function is an instance member then it:

If such a function is not an instance member then it:

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 compile const definition or other definitions that define constants. All attributes must be compile-time constants. For example:

compile const ipriv = internal static;
explicit namespace Version1;
explicit namespace Version2;
internal compile 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 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 has an implicit or explicit scope attribute, which designates a scope S to which the definition applies. 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
|  ExportBindingList , ExportBinding
ExportBinding 
   FunctionName
|  FunctionName = FunctionName

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() {}
  }

  interface I1 {
    function get length() {}
  }

  interface I2 {
    function get length() {}
  }

  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

    export length = I2::length; // Resolve name conflict in favor of I2
  }
}

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 Friday, April 12, 2002
previousupnext