# Capturing Calls to Undefined Properties

The TADS 3 language doesn't require you to specify the types of variables, functions, and properties when you declare them - in fact, the language doesn't have any way of making these declarations even if you wanted to. This means that the language isn't "statically typed": you can't tell the type of a variable with certainty just by looking at the variable's definition.

(Note that this isn't to say the language is "weakly typed"; a weakly typed language is one that allows different types to be used more or less interchangeably, so that the interpretation of a value depends on the context in which it's used. In a weakly typed language, it's always up to the programmer to specify the correct interpretation for a value each time it's used; for example, you might have to call one function to display a value as a string, and a different function to display the same value as an integer. TADS 3 is a strongly typed language, but it is not statically typed; it is instead run-time typed, which means that each value has a fixed type that cannot change, but a particular variable can hold values of any type. In a statically typed language, the variables keep track of the types; in a run-time typed language, the values themselves carry the type information.)

Because the compiler doesn't know in advance what kind of object a variable might contain, the compiler can't determine whether or not a particular property will be defined for the object. For example, consider this code:

    local x;
    x = getSomeObject();
    x.name;

Because the compiler can't tell what kind of object x will contain when this code is executed, the compiler can't know whether or not that object will define the property "name."

When you call a property (or, equivalently, a method) on an object, and the object doesn't define that property and doesn't inherit it from any superclass, the VM will do one of two things:

- If the program exports a property called propNotDefined, and the object defines or inherits this property, the VM invokes propNotDefined with the original property ID as the first argument, followed by all of the other arguments of the original invocation.
- Otherwise, the undefined property invocation returns nil.

The basic system library doesn't export any symbol called propNotDefined, so in a low-level TADS 3 program, you must explicitly export this symbol if you want to use the propNotDefined mechanism. However, note that the adv3 library does export propNotDefined, so the mechanism is enabled automatically if you're writing a library-based game.

Refer to the section on exporting symbols for details on the export mechanism.

## Throwing an Exception for Undefined Properties

Some library authors might decide that calls to undefined properties are inherently incorrect, and so choose to treat such calls as errors. The propNotDefined mechanism can be used to accomplish this, as shown in the code below.

    // this export is needed only if the library doesn't
    // otherwise define it
    property propNotDefined;
    export propNotDefined;

    // an exception for invoking an undefined property -
    // note that reflection could be used to provide a better message
    class PropNotDefinedException: Exception
      construct(prop, argList) { prop_ = prop; argList_ = argList; }
      displayException() { "call to undefined property"; }
      prop_ = nil
      argList_ = nil
    ;

    // throw an exception for any undefined property invocations
    modify Object
      propNotDefined(prop, [args])
      {
        throw new PropNotDefinedException(prop, args);
      }
    ;

## Proxy Objects

It's frequently useful to define one object as a "proxy" for another, so that the proxy object redirects most method calls to its underlying object. This allows the proxy to provide its own definitions for a few particular properties, while letting the original object do everything else. The propNotDefined mechanism makes this easy to implement.

    // redirect everything but 'name' to the original
    class Proxy: object
      construct(original) { orig_ = original; }

      // change the name
      name = "proxy for <<orig_.name>>"

      // redirect everything we don't define ourselves
      propNotDefined(prop, [args])
      {
        // call the undefined property on the original object
        orig_.(prop)(args...);
      }

      // my underlying object
      orig_ = nil
    ;

TADS 3 System Manual
Table of Contents | The Language > Capturing Calls to Undefined Methods
Prev: Anonymous Functions Next: Reflection