10.5 Declaration Merging

Namespaces are “open-ended” and namespace declarations with the same qualified name relative to a common root (as defined in section 2.3) contribute to a single namespace. For example, the following two declarations of a namespace ‘outer’ might be located in separate source files.

File a.ts:

  1. namespace outer {
  2. var local = 1; // Non-exported local variable
  3. export var a = local; // outer.a
  4. export namespace inner {
  5. export var x = 10; // outer.inner.x
  6. }
  7. }

File b.ts:

  1. namespace outer {
  2. var local = 2; // Non-exported local variable
  3. export var b = local; // outer.b
  4. export namespace inner {
  5. export var y = 20; // outer.inner.y
  6. }
  7. }

Assuming the two source files are part of the same program, the two declarations will have the global namespace as their common root and will therefore contribute to the same namespace instance, the instance type of which will be:

  1. {
  2. a: number;
  3. b: number;
  4. inner: {
  5. x: number;
  6. y: number;
  7. };
  8. }

Declaration merging does not apply to local aliases created by import alias declarations. In other words, it is not possible have an import alias declaration and a namespace declaration for the same name within the same namespace body.

TODO: Clarify rules for alias resolution.

Declaration merging also extends to namespace declarations with the same qualified name relative to a common root as a function, class, or enum declaration:

  • When merging a function and a namespace, the type of the function object is merged with the instance type of the namespace. In effect, the overloads or implementation of the function provide the call signatures and the exported members of the namespace provide the properties of the combined type.
  • When merging a class and a namespace, the type of the constructor function object is merged with the instance type of the namespace. In effect, the overloads or implementation of the class constructor provide the construct signatures, and the static members of the class and exported members of the namespace provide the properties of the combined type. It is an error to have static class members and exported namespace members with the same name.
  • When merging an enum and a namespace, the type of the enum object is merged with the instance type of the namespace. In effect, the members of the enum and the exported members of the namespace provide the properties of the combined type. It is an error to have enum members and exported namespace members with the same name.

When merging a non-ambient function or class declaration and a non-ambient namespace declaration, the function or class declaration must be located prior to the namespace declaration in the same source file. This ensures that the shared object instance is created as a function object. (While it is possible to add properties to an object after its creation, it is not possible to make an object “callable” after the fact.)

The example

  1. interface Point {
  2. x: number;
  3. y: number;
  4. }
  5. function point(x: number, y: number): Point {
  6. return { x: x, y: y };
  7. }
  8. namespace point {
  9. export var origin = point(0, 0);
  10. export function equals(p1: Point, p2: Point) {
  11. return p1.x == p2.x && p1.y == p2.y;
  12. }
  13. }
  14. var p1 = point(0, 0);
  15. var p2 = point.origin;
  16. var b = point.equals(p1, p2);

declares ‘point’ as a function object with two properties, ‘origin’ and ‘equals’. Note that the namespace declaration for ‘point’ is located after the function declaration.