### 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 is not "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.

### 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

In some cases, it is 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

      ;