July 2000 Draft
JavaScript 2.0
Core Language
Definitions
previousupnext

Tuesday, June 6, 2000

Introduction

Definitions introduce new constants, variables, functions, classes, interfaces, and namespaces. All definitions can be preceded by zero or more attributes using the following syntax:

AnnotatedDefinition 
   Definition
|  Attributes Definition
Attributes 
   Attribute [no line break]
|  Attribute [no line break] Attributes
Attribute 
   AttributeIdentifier
|  final
|  package
|  private
|  public
|  static
|  volatile
Definition 
   ExportDefinition Semicolon
|  VariableDefinition Semicolon
|  FunctionDefinition
|  ClassDefinition
|  InterfaceDefinition
|  NamespaceDefinition Semicolon

Attributes

An attribute is an identifier that modifies a definition. Attributes can specify a definition's scope, namespace, semantics, and other hints. A JavaScript program may also define and subsequently use its own attributes.

The table below summarizes the predefined attributes.

Category Attribute Behavior
Scope local The definition is local in the enclosing block.
scope The definition applies to the enclosing scope.
global The definition applies to the enclosing package.
Namespace private If the definition's scope is a class or interface, makes the definition visible only in the class's or interface's private namespace; otherwise, behaves the same as package.
package Makes the definition visible only in the package's private namespace.
public Makes the definition visible anywhere.
Visibility Modifiers implicit This definition can be made available as a variable using a use statement.
explicit This definition cannot be made available as a variable using a use statement.
indexable This definition can be accessed using the [] operator.
nonindexable This definition cannot be accessed using the [] operator.
enumerable This definition can be seen using the for-in statement.
nonenumerable This definition cannot be seen using the for-in statement.
Semantic   static The definition creates a global member (rather than an instance member) of the enclosing class or interface. The definition's scope must be a class or interface.
instance The definition creates an instance member (rather than a global member) of the enclosing class or interface. The definition's scope must be a class or interface.
final The definition cannot be overridden in subclasses. The definition's scope must be a class or interface.
virtual The definition can be overridden in subclasses using an identical type signature only. The definition's scope must be a class or interface.
volatile The definition can be overridden in subclasses using any type signature. The definition's scope must be a class or interface.
abstract No definition is supplied for this function in this class or interface. Calling this function throws an exception unless the function is overridden in a subclass.
concrete The opposite of abstract.
traditional Allows a function to access this and be used as a constructor even though it is not an instance method. See traditional functions.
Hint override The definition overrides a member of a superclass.
mayOverride   The definition may override a member of a superclass.
compile Compiler hint that the definition may be processed at compile time.
unused Compiler hint that the definition is not used.

If multiple conflicting attributes are used, the latter ones take precedence, so static final instance is the same as final instance.

Scope Attributes

A scope attribute describes the scope to which a definition applies.

The local attribute applies the definition to the enclosing Block. If the enclosing block is a class or interface, the definition does not appear as a member of that class or interface. If the enclosing block is a package, the definition does not appear as a member of that package, so other packages importing this one will not see this definition.

The scope attribute applies the definition to the enclosing scope. If the enclosing scope is a class, interface, or package, the definition will appear as a member of that class, interface, or package.

The global attribute applies the definition to the enclosing package. The definition will appear as a member of that package.

Unless changed, the default scope is scope.

Namespace Attributes

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

Every package has a predefined, anonymous namespace PackageSecrets. That namespace is attached to all definitions with the package attribute in that package. The package's scope includes an implicit use namespace PackageSecrets statement around the package that grants access to these definitions from within the package only.

Every class and interface has a predefined, anonymous namespace ClassSecrets. That namespace is attached to all definitions with the private attribute in that class or interface. The class or interface's scope includes an implicit use namespace ClassSecrets statement around the class or interface that grants access to these definitions from within that class or interface only.

Unless changed, the default namespace is public, which grants unrestricted access to use the definition. If at least one namespace attribute is provided, then the default namespace is ignored. If more than one namespace attribute is provided, then the definition's namespace is the union of the given namespaces and the definition can then be accessed through any of these namespaces.

Visibility Modifier Attributes

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

explicit and implicit

The explicit modifier prevents the definition from being accessed as an unqualified variable through a use statement. The definition can still be accessed using a namespace qualifier or by accessing it as a member of an object. For example,

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

package P2 {
  use import P = P1; // Imports P1 and then uses it
  c1;                // OK; evaluates to 5
  c2;                // Error: c2 not defined
  P.c2;              // OK: member access of object P
  P::c2;             // OK: package P used as a qualifier
}

explicit is used to add public definitions to a package P without having them conflict with definitions in other packages that import and use package P.

The implicit modifier is the opposite of explicit; implicit is the default.

indexable and nonindexable

An indexable definition can be accessed using the default [] operator. A nonindexable definition cannot be accessed using this operator. The default is indexable for public definitions and nonindexable for non-public definitions.

enumerable and nonenumerable

An enumerable definition can be seen by the for-in iteration statement. A nonenumerable definition cannot be seen by such a statement. The default is enumerable for public definitions and nonenumerable for non-public definitions. enumerable implies indexable.

Semantic Attributes

The static attribute makes the definition create a global member rather than an instance member of the enclosing class or interface. The instance attribute reverses this -- it makes the definition create an instance member of the enclosing class or interface.

The final attribute prevents subclasses from overriding this definition in the same namespace. virtual allows subclasses to override this definition but requires the overriding definition to have exactly the same type signature as this definition. volatile allows subclasses to override this definition without type signature restrictions.

Unless changed, the default attributes are instance volatile concrete for function (method) definitions and instance final concrete for other (constant, variable, and class) definitions of a class's or interface's members.

These attributes may only be used on definitions that apply to a class or interface. They cannot be used on definitions that, for instance, create local variables inside a function.

Hint Attributes

The override and mayOverride attributes control warnings. Normally defining a class or interface member with the same name as a visible member of a superclass generates a warning. The override attribute reverses the sense of the warning so that the warning will be generated if there is no visible member of a superclass with the same name. The mayOverride attribute turns off this warning altogether.

The compile attribute is a hint that the definition may be evaluated early. See compiler blocks.

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

User-Defined Attributes

A user-defined attribute may be defined using the following syntax:

AttributeDefinition 
   attribute [no line break] Identifier =
|  default =
|  AttributeDefinition Attribute
|  AttributeDefinition namespace ( TypeExpressionallowIn )

The first Identifier is the name of the attribute. The list of attributes after the = is its expansion; that list may be empty.

Identifier may be used as an Attribute until the end of the enclosing block. The names of attributes are separate from and do not interfere with the names of variables, functions, classes, etc. If an attribute is defined with a name that matches a non-reserved keyword (other than get or set) then that keyword's normal meaning becomes inaccessible until the end of the enclosing block. It's best to avoid such a conflict, but this rule allows JavaScript to evolve by adding new non-reserved keywords without breaking existing programs that happen to use them as attributes.

If namespace(N) is put on the right side of an AttributeDefinition, then the newly defined attribute, when used, will attach definitions to namespace N. For security reasons namespace N must be defined in the current package (if N were imported from another package P then it might be possible to, for instance, override some of P's final methods).

If default is used instead of attribute id, then the default attribute is changed to the right side of the AttributeDefinition. This default remains in place until another default attribute definition in the same block or the end of the block. The default does not propagate into subblocks of the enclosing block.

As an example, the following code creates aliases priv and loc of the attributes private static and local:

attribute spriv = private static;
attribute loc = local;
explicit namespace Version1;
explicit namespace Version2 extends Version1;
attribute V1 = namespace(Version1);
attribute V2 = namespace(Version2);

class C {
  priv var x;
  V1 var simple;
  V2 var complicated;
  priv const a:Array = new Array(10);
  priv instance var b;  // Instance variable

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

Definition Extent

Each defined name has a particular static and dynamic extent. The static extent is the region of source code where the name is visible (as long as the namespaces, if any, match and the name isn't shadowed by a more local definition). The dynamic extent is the time interval during which the defined constant, variable, function, class, or interface may be accessed.

Static Extent

Each definition or declaration D of a name n has an implicit or explicit scope attribute, which designates a scope A to which the definition applies. In general, n's static extent is the portion of scope A beginning with D and ending with the end of scope A; n is invisible from code located textually prior to D. If n is declared or defined several times inside a scope, its static extent begins with the first declaration or definition.

There are a few cases where an implicit declaration of n is inserted prior to the actual definition of n. These rules apply only if n is an unqualified name.

The first three rules permit the definition of recursive functions, classes, and interfaces without predeclaring them first. The fourth rule is for compatibility with JavaScript 1.5.

A definition D of a name n may be invisible even inside its static extent either because another, more local definition of n shadows D or because D specified a namespace that is not currently used.

Dynamic Extent

The dynamic extent of a definition or declaration D of a name n begins when D is executed. Each kind of a declaration and definition provides semantics that state how n may be subsequently accessed:

Kind Declaration Definition
const An attempt to read n generates an error until the first definition of n is executed. n's value is now readable.
var If there exists a coercion of undefined to n's type, n is set to the appropriately coerced undefined; otherwise, an attempt to read n generates an error until the first definition of n is executed. In either case, n may be written. n's value is now readable and writable.
function An attempt to read or call n generates an error until the first definition of n is executed. n is now readable and callable.
class n is now readable as a value of type type, but no other operations may be performed on it. Instances of n may be created and methods of n may now be called. n may now be used as a base class.
interface n is now readable as a value of type type, but no other operations may be performed on it. Methods of n may now be called. n may now be used as a base interface.
namespace n is now usable as a namespace.
export n is now usable in the same capacity as the original identifier.

These rules also apply to the implicit declarations of functions, classes, interfaces, and variables.

A definition or declaration D of a name n is invisible prior to the beginning of its dynamic extent. If an attempt is made to access n in this situation, the enclosing scopes are searched instead for an appropriate definition, if any, of n. Contrast the following programs:

const b:Integer = 1;

function f(c:Boolean):Integer {
  const a = b;
  const b;

  if (c)
    const b:Integer = 10;

  return a+b;
}

In the above program, f(true) returns 11, while f(false) throws an error in the return a+b statement because the inner b has been declared but not defined.

const b:Integer = 1;

function f(c:Boolean):Integer {
  const a = b;

  if (c)
    const b:Integer = 10;

  return a+b;
}

In the above program, f(true) returns 11, while f(false) returns 2. In the latter case, even though the return a+b statement is in the static extent of the const b:Integer = 10 declaration, it is not in that declaration's dynamic extent.

In general, it is not legal to define the same entity twice within a scope A without exiting A in the interim. There are a few exceptions:

Examples

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

var a0;                 // Public global variable
local var a1;           // Package-visible global variable
private var a2 = true;  // Package-visible global variable
package var a3;         // Package-visible global variable
public var a4;          // Public global variable
global var a5;          // Public global variable

if (a2) {
  var b0;               // Public global variable
  local var b1;         // Local to this block
  private var b2;       // Package-visible global variable
  package var b3;       // Package-visible global variable
  public var b4;        // Public global variable
  public var b5;        // Public global variable
}

public function F() {   // Public global function
  var c0;               // Local to this function
  local var c1;         // Local to this function
  private var c2;       // Local to this function
  package var c3;       // Local to this function
  public var c4;        // Local to this function
  global var c5;        // Public global variable
  package global var c6;// Package-visible global variable
}

private function G() {  // Package-visible global function
  var d0;               // Never defined because G isn't called
  global var d1;        // Never defined because G isn't called
}

class C {               // Public global class
  var e0;               // Public class instance variable
  private var e1;       // Class-visible class instance variable
  package 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
  package static var e6;// Package-visible class-global variable
  public static var e7; // Public class-global variable
  local var e8;         // Local to class C's block

  if (a2) {
    var f0;             // Public class instance variable
    private var f1;     // Class-visible class variable
    package var f2;     // Package-visible class variable
    public var f3;      // Public class variable
  }
  public function I() {}// Public class method
}

F();

A static subset of JavaScript 2.0 would disallow definitions inside a function F that define names in a scope outside F. This would disallow functions F and G above (as well as legacy programs that define global variables from within functions by neglecting to use var altogether).

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 {
  use import Light;

  explicit namespace New;
  explicit namespace Improved;
  attribute New = namespace(New);
  attribute Imp = 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 {
    package 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
    Imp 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 a union of namespaces package protected.


Waldemar Horwat
Last modified Tuesday, June 6, 2000
previousupnext