February 22, 2017·6 min read·engineeringtypescriptobject-typesmixins

TypeScript 2.2 distinguishes objects from primitive values

TypeScript 2.2 adds a non-primitive object type and understands mixin classes, while relaxing property access through string index signatures.

An empty object type sounds like a reasonable declaration for an API that accepts objects. In TypeScript, that intuition has been misleading. Structural compatibility allows primitive values to match {}, so the spelling cannot express the requirement that a value really be an object. TypeScript 2.2 adds a lowercase object type to describe that requirement, giving declarations a way to reject primitive inputs without inventing a collection of unrelated properties.

The February 22 release announcement uses Object.create as the motivating example. It also describes improved support for mixin classes and more permissive access through string index signatures. These changes concern different JavaScript patterns, but each makes an existing runtime distinction easier to express accurately in a TypeScript program.

I am interested in these corrections because framework APIs frequently depend on exactly what kind of value they receive. TypeScript helps web development when its declarations describe those conditions faithfully. A type with a reassuring name but broader compatibility than the API accepts can leave the application with less protection than its author expects.

Lowercase object has a specific exclusion rule

The new type excludes string, boolean, number, and symbol. With strictNullChecks, it also excludes null and undefined. Rosenwasser's announcement distinguishes it from both {} and the capitalized Object type. The spelling is therefore meaningful, rather than a stylistic preference among equivalent ways to declare the same thing.

The strict-null qualification belongs in the explanation. A reader using a different null-checking mode cannot infer every assignment rule from an example that assumes strict null checking. The TypeScript 2.0 announcement introduced that opt-in distinction because treating null and undefined as separate types changes compatibility. The new object type operates within the selected checking mode.

For an API requiring a non-primitive argument, a declaration can now identify that requirement directly. The type does not describe the argument's individual properties. It is useful when the distinction between an object and a primitive is what the API needs to establish before doing anything more specific.

function acceptObject(value: object): void {
}

acceptObject({ enabled: true });
acceptObject([1, 2]);
// acceptObject(42); // rejected in TypeScript 2.2

The array is still an object, so it is compatible with this broad requirement. The number is not. A declaration needing an enabled property should express that property as well, because object alone does not promise it. Choosing the new type solves the non-primitive distinction without replacing the rest of structural typing.

This is also why the new type is useful in generic constraints. A library can state the minimum category of value it accepts while retaining a more specific type parameter for information supplied by the caller. The broad constraint need not erase the caller's own properties. That relationship becomes important in the mixin example included with the release.

A mixin takes a constructor and returns a class

The announcement describes mixins as an existing JavaScript pattern the compiler should understand. A function receives a base constructor and creates a class extending it. The returned constructor combines the base class's behavior with members added by the mixin. Supporting this pattern requires the checker to reason about a base class that is supplied through a generic parameter.

TypeScript 2.2 loosens class restrictions and adjusts how intersection types operate. The announcement says those changes allow a class to extend something that constructs an object type. Its example declares a constructable type with a rest-argument constructor signature returning object, then constrains the supplied base constructor by that type.

The distinction is between the class value and an instance. A mixin receives the constructor so it can extend it. The constraint describes what calling that constructor with new produces. Confusing those levels would lead to a declaration that accepts an instance where the implementation actually needs a base class.

The release's Timestamped example adds a private timestamp field and a getter. Applying it to a Point constructor yields a class retaining the point's properties and gaining the timestamp. A subsequent SpecialPoint class extends that result and adds a further coordinate. The example demonstrates composition through constructors while preserving information about instances at the end.

This matters to framework integrations because a framework may already provide a class-producing API. The announcement mentions bridging ES2015 classes with Ember's Object.extend. TypeScript does not need every framework to abandon its pattern before the checker can describe the result. The language work instead makes the constructor relationship expressible in the existing style.

I prefer that direction to asking an application to reorganize solely around limitations of its declarations. There is still complexity in generic constructor types, and a library author needs to explain what the mixin adds. The benefit is that application code can consume the resulting class with knowledge of both its inherited properties and the added behavior.

String indexes permit dot access

The release also changes a smaller but common inconsistency. A string index signature describes a type used like a dictionary. Before 2.2, looking up an undeclared property through that signature required bracket syntax, even when the property name could be written as an ordinary identifier. The new release permits the corresponding dot access.

interface Flags {
    [name: string]: boolean;
}

declare const flags: Flags;
if (flags.debugMode) {
    // use the option
}

The signature says that string-keyed access has a boolean result. The announcement explains that JavaScript's x.propName and x["propName"] refer to the same property. Permitting both forms makes the checking rule align with the way the dictionary is accessed at runtime. The declaration remains responsible for the claimed value type.

There is a tradeoff in such a broad signature. It permits names according to the string-index rule, rather than limiting callers to a closed list of explicitly declared properties. A developer who intends a fixed configuration schema needs to describe that schema. Dot syntax becoming legal does not establish that an arbitrary property is actually present in a particular runtime object.

The change is therefore about consistency between access forms. It does not add validation of parsed JSON or turn every key into a known member. The announcement's configuration example becomes more ergonomic because the index signature already provides the type rule. Code that needs a finite property set continues to benefit from declarations expressing that narrower set.

Constructors can inspect the construction target

Another addition, new.target, gives a constructor information about whether a subclass is being constructed. The announcement identifies it as an ES2015 meta-property and points out a relevant restriction. An ES2015 constructor cannot access this before calling super(). An operation that needs construction information at that stage cannot simply obtain it from the instance.

This capability concerns execution inside a constructor, whereas the mixin constraint describes a constructor to the checker. They address different levels of the class mechanism. A declaration identifies which constructors an API accepts, and new.target lets executing code examine the construction target. Keeping those roles distinct helps explain why constructor-oriented programming can need both a richer type description and access to an existing JavaScript language facility.

The feature also illustrates the release's commitment to JavaScript patterns. TypeScript's object and class checks have to coexist with the behavior that class constructors already expose. The new meta-property gives source code a way to express that behavior without requiring a separate framework convention to pass the information around.

Editor fixes use the same model

TypeScript 2.2 adds quick fixes for missing imports and properties, among other errors. It can also add a forgotten this. or implement abstract members. The announcement treats these as language-service capabilities available through editor plugins. They are useful because a diagnostic can lead directly to a proposed source change in the same environment where the programmer is working.

These actions have different meanings from accepting more programs in the type checker. Adding an import changes source code, while the object type changes how a declaration can constrain source code. An editor suggestion still needs the intended program structure to be correct. A missing property might mean the class needs another member, or it might mean the access contains a typo.

The release also adds a react-native JSX emit mode. It preserves JSX syntax while writing .js output, matching the input-file behavior described for React Native in the announcement. This is another example of a tool integration depending on a concrete contract beyond type compatibility. Keeping JSX for another transform and choosing the expected output extension are separate decisions that this mode combines.

The resulting release improves both the declarations available to library authors and the operations available to their users. A non-primitive constraint can describe what a constructor produces, a mixin can retain its base instance information, and an editor can propose a change using that model. These improvements matter when the same framework pattern must remain understandable across the source declaration and the application that consumes it.