TypeScript 4.3 beta separates property input from output
The TypeScript 4.3 beta lets setters accept broader types than getters return. Explicit override declarations also let the checker detect a broken inheritance relationship.
A property that normalizes input can accept more types than it returns. A setter might accept a string representation while its getter always returns a number. The TypeScript 4.3 beta announced on April 1 lets declarations express that difference. This removes the need to weaken the read type merely to describe the values accepted by writes.
The same beta introduces override and noImplicitOverride for class members. Both additions make an intended relationship explicit. Accessor types describe how an input becomes an observable value, while an override declaration states that a derived member depends on a corresponding base member.
I find TypeScript most useful when its declarations follow the semantics of the underlying API closely. An interface that accepts strings but returns normalized numbers should be able to say so. Otherwise, the declaration either rejects valid use or gives consumers less useful information than the implementation provides.
A setter can accept a broader type
The beta announcement uses numeric conversion to motivate separate accessor types. Declaring the property as unknown would force readers to narrow it before use, even though the getter always returns a number. Declaring it as any would remove useful checking. Requiring only numbers would reject inputs that the setter intentionally supports.
class Count {
private stored = 0
get value(): number {
return this.stored
}
set value(input: number | string) {
const converted = Number(input)
this.stored = Number.isFinite(converted) ? converted : 0
}
}
const count = new Count()
count.value = "12"
const normalized: number = count.value
The setter performs the conversion at runtime. The types describe that operation without adding it automatically. A different setter implementation could satisfy the same broad signature while applying a different policy to invalid numeric text. Consumers still need the API's documented conversion semantics.
The beta requires the getter type to be assignable to the setter type. In this example, a number is an accepted input, so assigning the property's own read value back to it remains permitted. The rule prevents arbitrary disagreement between the two sides, even though the write side can be broader.
Separate accessors also work in object literals and in interface declarations. The benefit therefore extends to typing existing property APIs, rather than requiring every consumer to adopt a particular class implementation.
Type transformations do not retain every write capability
Ryan Cavanaugh's accessor implementation discussion explains limits around transformed and indexed types. An indexed access such as T[K] still denotes the read type. A generic helper built around that expression does not automatically gain the property's broader setter input type.
That limitation is useful to know when designing abstractions around coercing properties. Direct assignment can accept an input that a helper using the read type rejects. The helper's declaration has lost the specific write contract, even though both operations eventually refer to the same property name.
The implementation discussion likewise treats mapped types in terms of their read-side information. A transformed copy cannot safely assume that it retains the original setter's conversion behavior. This keeps a declaration about values separate from a promise about how a particular object processes assignments.
Overrides can outlive the base method they replaced
Inheritance has a different missing relationship. A derived class can define a method with the same name as a base method without stating whether the match is intentional. If the base class later renames that method, the derived method may remain as an unrelated member. The compiler needs an explicit dependency to report that break.
Marking a member override asks the checker to verify that the base class contains the corresponding member. The beta announcement illustrates a base class replacing separate visibility methods with one method. Explicit override declarations let the stale derived methods fail checking after that change.
noImplicitOverride addresses the opposite omission. When enabled, an overriding method must declare its intent. A newly added helper in a derived class cannot accidentally replace a base method without attracting a diagnostic. The option makes the relationship mandatory where the compiler detects it.
These checks have distinct triggers. An explicit modifier can detect that a once-related base member disappeared. The flag can detect that an undeclared relationship exists. Using the modifier alone does not establish that every overriding member in the codebase has been marked.
For a library that exposes base classes to consumers, this relationship crosses a package boundary. A downstream override can depend on an upstream member even when the downstream class still has perfectly valid method syntax after the upstream declaration changes.
I prefer an explicit dependency when a subclass relies on a base method's name. Refactoring that name should reveal affected implementations through checking. It is harder to assess a class hierarchy when the source does not distinguish an intentional override from a coincidental name collision.
The beta offers those declarations for evaluation alongside the accessor changes. Library authors can represent normalization without weakening reads, while class authors can state which members depend on inherited names. Each change gives the checker information that was present in the programmer's intention but absent from the earlier declaration.