TypeScript 3.5 strengthens object contracts and restores checking performance
Omit standardizes a common type transformation, while stricter union checks and unknown generic constraints expose assumptions previously accepted by the checker.
A type checker can reject useful programs, accept invalid ones or take too long to explain either result. TypeScript 3.5 addresses all three concerns in different parts of its implementation. Its new Omit helper is convenient, but the more consequential changes concern what the compiler assumes about objects and unconstrained values.
The May 29 release announcement describes a performance regression introduced by a bug fix in 3.4. It could cause a large increase in checker work, particularly affecting styled-components users. Because the same analysis supports editor operations, the regression also affected interactive work with JavaScript and TypeScript.
The release reports optimization of those paths alongside stricter checks. Improving speed does not simply mean accepting less responsibility for a program's relationships. The checker also repairs permissive cases and becomes more capable of recognizing some valid union assignments.
I find that combination relevant to TypeScript's usefulness in web development. Precise types help when feedback remains practical during ordinary edits. A type system whose analysis interrupts the editor can undermine the workflow that made its precision worth having.
Omit composes existing operations
The standard library declaration in the release source defines Omit using Pick and Exclude. It takes the keys of an input type, excludes the requested keys, then selects the remaining properties. The definition is small because the existing type operations already describe the transformation.
A common use is defining input that excludes a server-assigned property.
interface DocumentRecord {
id: string;
title: string;
body: string;
}
type NewDocument = Omit<DocumentRecord, "id">;
const draft: NewDocument = {
title: "Release notes",
body: "Changes awaiting publication"
};
The resulting type contains the title and body properties from the original interface. If either property's type changes there, the selected property in NewDocument follows it. The helper reduces duplication of that part of the contract.
It does not remove a property from a runtime object. The declaration is a type transformation, and the sample constructs an object without the omitted property. An existing object does not lose its id merely because another reference has a type that does not expose it.
This distinction matters for serialization and data disclosure. A type that omits a field can describe the desired data contract, but code producing an outgoing object must actually choose the corresponding fields. The compiler's view of a value is separate from the data present when JavaScript executes.
The release announcement also says the compiler uses Omit to express types from object-rest destructuring on generics. That connects the helper to an existing language operation. The type describes which properties remain after the operation, while JavaScript performs the actual object construction.
A standard name can collide with a local helper
Many projects already define their own Omit because the operation is common. Adding the name to the standard library can produce a duplicate-identifier error when a project has a global declaration with that name. The announcement includes this among the release's compatibility changes.
The appropriate resolution depends on which definition the project intends to use. A duplicate can be removed in favor of the supplied utility, or an existing declaration can be exported from a module or placed in a namespace. That keeps an intentionally different helper from competing for the same global name.
This is a small example of how a convenience addition can affect existing code. The utility's definition is straightforward, but its availability in the global type environment changes the names a project shares with the standard library. Namespace placement is part of the compatibility question.
Union literals receive stronger property checking
TypeScript 3.5 improves excess-property checks for unions without discriminants. The announcement's example combines point coordinates with a label property of the wrong type. Earlier checking could let that incorrectly typed property through when comparing the literal against the union.
The updated checker verifies that supplied properties belong to some union member and have an appropriate type. It can therefore reject a Boolean label when the label member requires a string. That detects a mismatch in data supplied by an object literal.
The rule does not require every object to match only one member without overlap. The announcement explicitly permits partial overlap when the property types are valid. An object combining numeric coordinates and a string label can still satisfy the illustrated union.
This boundary matters when interpreting a new error. The change is not an exact-object-type system that bans every additional property in every context. It is a correction to the checks applied to literals against the union. Explaining the affected comparison is more useful than saying unions have become universally stricter.
Discriminant checking also accepts more valid assignments
Another union change goes in the other direction. A source object with a Boolean field can now satisfy a destination union whose alternatives split that field into true and false cases, when the remaining data fits. Earlier checking compared the source against each member separately and could reject both.
The announcement describes decomposing the source according to possible discriminant values. Boolean has the possible values true and false. Considering those cases exposes that the source's possibilities are covered by the destination union.
The checker still needs to preserve relationships between fields. Combining every property's possible values independently would allow invalid pairings, such as a discriminator that requires text next to a numeric payload. The release's improvement recognizes the covered cases without discarding the correlations that make the destination useful.
That distinction is central to a discriminated interface. A caller depends on one property selecting the meaning of another. A checker that merely unions all property types would forget that relationship and permit values that a branch cannot handle.
Unconstrained generics now begin with unknown
The release changes the implicit constraint of a generic parameter without an explicit constraint. It becomes unknown, replacing the empty object type. The announcement explains that an unconstrained type parameter could be instantiated with values for which the old assumptions were too strong.
Unknown can include null and undefined, and it is not assignable to object merely by default. Operations inherited from Object are therefore not automatically available on an unconstrained generic value. A function that calls toString on such a parameter can now receive an error.
That error identifies a missing premise in the function's contract. If the function requires a non-nullish value with the previous empty-object constraint, it can declare that requirement explicitly. If it truly accepts any value, its implementation needs to account for the broader possibilities.
Failed inference also yields unknown where the old behavior yielded the empty object type. This affects callers using generic functions whose result type cannot be inferred from an argument. The result no longer acquires capabilities merely because inference lacked information.
The announcement connects this change to Object.keys checking for ES5. That operation requires an object in the relevant runtime, so a value inferred as unknown cannot satisfy the requirement automatically. The resulting diagnostic exposes an assumption about the argument before JavaScript reaches the operation.
I prefer that default because absence of information should remain visible. A generic annotation is useful when it preserves a relationship between inputs and outputs. It becomes misleading when an uninferred value appears to have properties the function never established.
Indexed writes need the right value for the chosen key
The announcement also repairs unsound writes through indexed access types. Reading a property through a key and writing a property through a key place different demands on the value. A generic key can name fields with different types.
An implementation that writes a fixed string for every permitted key can violate a numeric field's contract. TypeScript 3.5 rejects the permissive case described in the announcement. The relationship between the selected key and its corresponding value type must hold inside the implementation as well as at a call site.
This change can expose code that appeared generic while assuming a particular property type. The proper interface needs to express the relationship it actually supports. Suppressing the error without that premise transfers the unchecked assumption back to the author.
The release additionally caches module-resolution information and settings calculated from project configurations to reduce repeated incremental-build work. Those optimizations address build costs separately from the correctness changes. The announcement's performance results refer to its tested scenarios, including large project-reference builds.
TypeScript 3.5 supplies a standard spelling for omitting keys while making several existing contracts more accurate. Literal union checks inspect property types more carefully, discriminant analysis recognizes covered cases, and unknown prevents unconstrained generics from acquiring unsupported assumptions. These changes affect the checker evidence on which application code relies.