Motivation
Assume a generic HTML input element modelled by the following interface:
interface Input<TValue> {
getValue() : TValue;
setValue(value: TValue): void;
clear(): void;
onClick: Event;
//etc...
}
We want to create a derivation specialised to string
interface TextInput extends Input<string> {
// We specifically want this method to be called `setText`,
// since it is felt to be more user-friendly than `setValue`
setText(text: string) : void;
}
We now have a problem. The user needs to refer to the documentation in order to decide which method they should use:
var textInput : TextInput;
textInput.setValue('foo'); // or
textInput.setText('foo'); // ???
We effectively would like to rename the method (or change its signature), rather than creating a new one.
Proposal A
Introduce syntax - [identifier]:
interface TextInput extends Input<string> {
- setValue(value: number): void; // Error: invalid reference to inherited method
- setValue2(value: string): void; // Error: invalid reference to inherited method
- setValue(value: string): void; // Okay
setText(text: string) : void;
}
var textInput : TextInput;
textInput.setValue('foo'); // Error: method does not exist
textInput.setText('foo'); // Okay
The use-case above is slightly contrived, but the usefulness of providing more fine-grained control over interface declarations is hopefully clear.
To ensure casting is safe, we should require types that implement an interface also implement any hidden methods:
class TextInputClass implements TextInput {
public setValue(value: string) { /**/ } // Required
public setText(text: string) {
this.setValue(text):
}
}
Proposal B
Introduce an @obsolete decorator.
interface TextInput extends Input<string> {
@obsolete
setValue(value: string): void;
setText(text: string) : void;
}
This does the following:
Having a sub-type that has less members than the parent type really breaks OO though. Maybe you can turn this into a proposal for a @Deprecated annotation in the upcoming 1.5?
@billccn, I agree that this is uncharted territory as far as OO goes. But it's possible to do a lot of weird things in JavaScript.
Also I don't see this as deprecation. Let's assume we are in full control and don't care about breaking changes etc. This is more about fine-tuning the API for certain classes so that they are easy to use and understand.
Do you have a specific example where this breaks OO in the _structural sense_? Note that the underlying object will contain all hidden methods.
One of the basic OO principles, similar to what you've realized, is that sub-class must obey parent class contracts (including implementing all the methods). Existing code and other parts of the language are based on this assumption and they all needs to be checked if this is changing.
For example, someone creates a sub-type of the TextInput interface and define a new setValue(number) method. For the classes implementing this, it is unclear which version of setValue they should implement. For callers, it is unclear if calling setValue with string is valid or not.
From your examples, what you really want is to generate some compiler messages when the unwanted methods are used and hide them from tools and this is exactly what Java's @Deprecated (or C#'s Obsolete) annotation can do.
someone creates a sub-type of the TextInput interface and define a new setValue(number) method
We could possibly let everything else work as normal:
interface NumberAndTextInput extends TextInput {
setValue(value: number): void; // Error: Types of property setValue are incompatible
setValue(value: number|string) : void; // Okay
}
I doubt there is something we want to do here beyond supporting some decorators that might be generally useful (obsolete/deprecated and perhaps one that you can use to hide a member from appearing in Intellisense). I would much rather someone have to occasionally check the documentation to understand members (this particular case feels entirely doable just in doc comments that would surface in signature help, you wouldn't even have to leave VS) than have to constantly check whether types are structurally equivalent.
This issue should be marked as fixed.
type StringLiteralDiff<T extends string, U extends string> = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T];
type Omit<T, K extends keyof T> = Pick<T, StringLiteralDiff<keyof T, K>>;
interface Input<TValue> {
getValue(): TValue;
setValue(value: TValue): void;
clear(): void;
}
interface TextInput extends Omit<Input<string>, 'setValue'> {
setText(text: string): void;
}
let x = {} as TextInput;
x.setValue('s'); // We get the expected error: Property setValue does not exist on type 'TextInput'
Although, older and wiser now, I wouldn't recommend anyone actually doing that!
@NoelAbrahams In Context of your code, How can I remove Multiple Properties?
@besrabasant, you can provide a union to Omit.
interface TextInput extends Omit<Input<string>, 'setValue' | 'clear'> {
setText(text: string): void;
}
let x = {} as TextInput;
x.clear // Required error
@NoelAbrahams thanks. I knew about union types but never thought of that.:blush:
Most helpful comment
This issue should be marked as fixed.
Although, older and wiser now, I wouldn't recommend anyone actually doing that!