Open dgreensp opened 6 years ago
It would probably be possible to write a custom tslint
rule to check that an interface redeclares all inherited members.
Can you elaborate on the value checking the interface declaration gives you vs checking the actual use? in the example above, you would get an error in createDragAndDropHandler
if the implementation did not conform to GestureHandler
.
it would be useful to be able to document and enforce the relationship between two interfaces without inheriting members.
The value of having the choice to write interface Foo implements Bar
for some interface declarations, rather than interface Foo extends Bar
, is 1) in some cases it reads much better to declare all members of Foo, not just the ones absent in Bar, and 2) I may not want to automatically inherit members that are later added to Bar! It would be a compiler error if someone adds a member to Bar that isn't in Foo.
To give another example, consider the difference between:
interface Point3D extends Point {
z: number;
}
and:
interface Point3D implements Point {
x: number;
y: number;
z: number;
}
The value of the latter is 1) It's more readable (especially if Point is in another file or package), and 2) if someone adds a member to Point, it doesn't automatically get added to Point3D. Instead, it becomes a conformance error.
Edit: In code today you could also write:
interface Point3D extends Point {
x: number;
y: number;
z: number;
}
However, the compiler will not guarantee that this is a complete specification of Point3D (so you still have to go look up Point if you want the complete list of members), and (2) remains (potentially unwanted inheritance of members added to Point in the future).
What if an optional member is added to Point? With the proposed implements
feature, conformance holds and nothing more happens. With extends
, Point3D has gained a new member, which may or may not make sense. It probably won't make sense if the author of Point3D was using extends
merely to annotate conformance, not for inheritance. Then the declaration of Point3D must be updated to explicitly override the unwanted optional property.
Actually, inheriting unwanted optional members in interfaces is a problem in general, so let's promote it to benefit (3): When extending an interface with optional properties, you must explicitly "stamp out" all current and future properties you don't want, I think by writing foo?: never
. When implementing an interface with an interface (under the proposal), you can just list the ones you want.
Especially across module boundaries, implementing an interface is safer than inheriting from one.
And just having an interface Point3D
that does not extends
or implements
Point
, and get the error on the first invalid use, instead of on the interface?
@mhegazy You could say the same thing about class Foo implements Bar
and just get rid of the implements
keyboard altogether, since structurally the class either conforms to the interface anyway or it doesn't. If it's important that a class implements an interface, implements
allows you to write this explicitly and guarantee that conformance will be checked.
class Foo implements Bar
and just get rid of theimplements
keyboard altogether
Actually, I would rather we did :D
Heh. It's possible that the demand for this specific feature doesn't justify the cost. However, I don't agree with "just catch it at the use site" as the right attitude here, and in general, I am in favor of providing ways to tell the compiler that one type is supposed to be assignable to another, such that it will throw an error if not. Sometimes the way you have to construct a subtype of X (e.g. via an intersection type, or all sorts of ways) does not obviously produce a subtype of X. Relying on use sites is similar in spirit to relying on runtime: you'll get an error anyway, later, if your code exercises that case, so why do you need a better error, sooner, that you are certain to see? Why bother to explicitly write out the type you expect something to be?
For values, I'm already using the incantation const n: never = x
to "assert" that x is assignable to never (e.g. to make a checked-exhaustive if-else). I guess there is a similar incantation for types, something like putting this at the top level on a line by itself: (p: Point3D): Point => p;
.
I'm here with the same question.
At first, I thought that intersection can help enforce that confrontation, but I was wrong.
I think that use case it pretty much valid and basically the same as for class implements
— it's needed to explicitly say, that newly declared interface should always implement another interface (but it can be broader).
I think the question isn't about implements
. It's about finding a way to declare the fact, that some type at least should properly implement another type. It just happens that implements
seem to be a natural fit for it.
@ArmorDarks I think you can achieve the effect you want without any changes to typescript. You can have a type alias as the extends clause. The type alias can check if the currently declared interface extends the wanted interface in its type parameters but the actual result of this type alias will never add anything to the interface (it will be {}
).
interface A {
foo: string;
bar: number
}
type Implements<T, U extends T> = {}
interface B extends Implements<A, B> {
foo: string;
bar: number
}
Usually unused type parameters are a bad idea, but I think we can at least rely on the fact that the type parameter constrains will be checked on instantiation of the type alias even if they are not used afterwards (although I am ready to be contradicted on that by anyone with more insight into this).
The disadvantage of this is that you have to be aware that this exists and of its semantics, but ultimately using implements
would have the same drawbacks and the implements
solution might actually confuse a lot of people when they mistakenly use implements
and get different results then they would expect.
@dragomirtitian thanks for the suggested solution. I'm struggling with a similar issue that I'm quite sure is related to this thread. To use your example, what if I have the following:
interface A {
[key: string]: {
foo: string;
}
}
type Implements<T, U extends T> = {}
interface B extends Implements<A, B> {
bar: {
foo: string;
}
}
Typescript is throwing an error where Index signature is missing in type
, but what I would expect is for it to understand that bar
is a key string in that case
@dimabru The error is not wrong. You would get the same error for a class implementing such an interface (class BB implements A {}
causes Index signature is missing in type 'BB'
).
If you want to ensure all the interface are of the same type, this would work:
interface A {
[key: string]: {
foo: string;
}
}
type Implements<T, U extends T> = {}
interface B extends Implements<Record<keyof B, A[string]>, B> {
bar: {
foo: string;
}
}
Or a more encapsulated version:
type ConstrainValues<TItem, T extends Record<keyof T, TItem>> = {}
interface B2 extends ConstrainValues<A[string], B2> {
bar: {
foo: string;
}
}
Another use-case this would allow: interfaces implementing arbitrary types.
E.g. I'd like to ensure that an interface/type definitely has keys type KeyNames = "a" | "b" | ...
, then I can enforce that by making sure it implements Record<KeyNames, unknown>
(no restriction on values)
Currently I can only place this restriction on values. But I'd like to be able to put it on interface definitions as well.
@peey You can do that too with my previous suggestion:
interface A {
foo: string;
bar: number
}
type Implements<T, U extends T> = {}
type KeyNames = "a" | "b"
interface Bad extends Implements<Record<KeyNames,unknown>, Bad> {
}
interface Ok extends Implements<Record<KeyNames,unknown>, Ok> {
a: number,
b: string
}
@dragomirtitian thanks! Your suggestion looks really useful. Although its code may look trivial, I believe that it's worthy of being documented better and being shown to more developers.
I think making this a part of the typescript built-in utility types would be a good resolution to this issue.
I read up on this after I ran into another interesting use-case. I was focusing on using generics to auto-generate some property keys on an extended interface.
I realized to achieve this, since I'm using multiple levels of extend
, I would have to pass my generic type variable all the way through my inheritance tree to get this to work. This ends up becoming very verbose, and feels cumbersome.
export type SpeechMetaData<T> = {
[P in keyof Omit<T, "speechMetaData">]: Speech | undefined; // <== This gives me nice code-completion for me and my team
}
/**For objects to inherit from */
export interface Speakable<T> {
speechMetaData?: SpeechMetaData<T>;
}
export interface Question<T> extends Speakable<T> {
instruction: string;
sentence: string;
type: string;
}
All of that seems OK, and I don't believe there is a better way to do this currently. The problem occurs later where I want to use Question
as a base-type on other parts of my project. Here is an example of what I mean
export abstract class GameQuestion<T extends Question<T>> implements IGameQuestion<T> {
...
}
While this doesn't seem too bad, the more I need the type-variable T
to auto-generate these properties on SpeechMetaData
, the more I have to pass it up to inheriting question types.
export interface SelectTheWordQuestion<T> extends Question<T> {
...
}
export interface SpecificWordSelectionQuestion extends SelectTheWordQuestion<SpecificWordSelectionQuestion> {
...
}
This can potentially be acceptable if the inheritance is finite, and you're okay with the level of verbosity with these generic types.
implements
)export interface Question implements Speakable<Question> {
instruction: string;
sentence: string;
type: string;
speechMetaData?: SpeechMetaData; // Is type-var `Question` inferred here, or do we still need the type variable? I don't know.
}
export interface SpecificWordSelectionQuestion extends SelectTheWordQuestion implements Speakable<SpecificWordSelectionQuestion> {
...
speechMetaData?: SpeechMetaData;
}
While this doesn't seem like a big change, now my type variable T
is no longer required on the Question
interface, which I believe will be easier to use for my team members.
The important thing to consider while exploring this use-case is that generic type T
is only used for Spekable
interface, and isn't utilized in the Question
interface, so truthfully, I believe it shouldn't need to be passed down into Question
. The implements
keyword on interfaces would help me get around this as shown.
Another use case for this feature might be making sure an interface is a partial of something else:
interface Thing {
a: number;
b: number;
}
interface ThingSnapshot implements Partial<Thing> {
a: number;
}
So it makes sure you can only define keys which can also be found in Thing
. Reversed multiple inheritance.
Hope I didn't misunderstand the issue.
@phil294 This is why I came here. In GQL, it is common for frontend projects to use codegen to pull the schema introspection into TS and help with type safety. The problem is that in GQL I may have a query that returns a user object that has 10+ different properties, but when actually calling this query, I might only pull in 2 or 3 of them. Defining this response as Partial<User>
means I will not be sure what is present or not without cross-referencing the actual GQL query string every time. Defining the response manually means I might accidentally make a typo or mis-type something.
Instead, if I could do interface UserResponse implements Partial<User> {
means I can include just the fields I am actually loading and not worry about typos or incorrect types. This seems like a huge win for any GQL project unless there is a way to already accomplish this today that I am not aware of.
@bduff9 why wouldn't you use interface UserResponse extends Pick<User, "name" | "age"> {
in that scenario?
@RyanCavanaugh That is certainly a possibility and is something I sometimes use, however the 2 issues with that is 1) I cannot modify the fields at all (i.e. mark something as optional if it is not optional in User) and 2) While subjective, I feel this is less readable than the alternative, where you have the fields and definitions listed in the interface vs. a list of pipe separated strings. If I do specify fields in the interface instead of leaving it empty to make it more clear, I run into the same problem where I can define a field that doesn't exist in the model or can define a field with the wrong typing by accident as the extends provides no type safety like the implements would.
I have another use case for this. It would be convenient to have an interface implement another to specialise the type of a field while also catching potential typos:
interface Base {
field: string;
}
// currently
interface Specialised extends Base {
Field: 'value1' | 'value2'; // oops typo, Specialised will now have two fields on it, no compilation errors
}
// desired behaviour
interface Specialised implements Base {
field: 'value1' | 'value2'; // ok
}
interface Specialised implements Base {
Field: 'value1' | 'value2'; // compilation failure
}
Another solution would be to be able to mark a field as explicitely overriding a field in the base interface. So maybe something like this ?
interface Specialised extends Base {
override field: 'value1' | 'value2';
}
Edit: The Implements
utility type shown above seems to work for my usecase, but it's not very readable.
Maybe this is a better readable approach to reach the behavior: https://www.codegrepper.com/code-examples/typescript/typescript+override+interface+property
Based on the example above I made this, a lot readable imo despite it don't use interfaces
but types
type GenericObject = {
[key: string]: any;
};
interface Services {
req: GenericObject;
res: GenericObject;
}
type Implements<T, R extends T> = R;
type MyNewService = Implements<Services, {
req: {
// request type
};
res: {
// response type
};
}>;
Any updates on this?
I also have difficulties with this and an interface implementing another interface would be a great improvement
I have a base interface IOrder with about 60 elements used example to represent an order I need to sent an email that consumes 20 elements of this interface that email's interface IMail needs to contain 20 elements of interface IOrder
interface IMail implements IOrder {
order : '12345';
id: '1234',
18 more fields ...
}
ok
interface IMail implements IOrder {
order : '12345';
ids: '1234',
18 more fields ...
}
Object literal may only specify known properties, but 'ids' does not exist in type ' IOrder'.
Did you mean to write 'id'?
thanks
I also have difficulties with this and an interface implementing another interface would be a great improvement
I have a base interface IOrder with about 60 elements used example to represent an order I need to sent an email that consumes 20 elements of this interface that email's interface IMail needs to contain 20 elements of interface IOrder
interface IMail implements IOrder { order : '12345'; id: '1234', 18 more fields ... }
ok
interface IMail implements IOrder { order : '12345'; ids: '1234', 18 more fields ... }
Object literal may only specify known properties, but 'ids' does not exist in type ' IOrder'. Did you mean to write 'id'?
thanks
TS1176: Interface declaration cannot have 'implements' clause.
I think this is useful.
Summary:
Currently: TS1176: Interface declaration cannot have 'implements' clause.
My use-case? In Angular 14, I have a ValueModel with most of the keys name that my form should have. I can get the keys from the ValueModel with
type ValueModelKeys = {[T in keyof ValueModel]: any}
then sync my FormModel with does keys name:
interface FormModel implements ValueModelKeys
I find this valuable.
I think this is useful.
Summary:
- implements clause to require an interface blueprint
Currently: TS1176: Interface declaration cannot have 'implements' clause.
My use-case? In Angular 14, I have a ValueModel with most of the keys name that my form should have. I can get the keys from the ValueModel with
type ValueModelKeys = {[T in keyof ValueModel]: any}
then sync my FormModel with does keys name:
interface FormModel implements ValueModelKeys
I find this valuable.
Found a solution that works
export class RoleFormModel implements Omit<RoleKeyModel, "id">{
We can use classes instead of interfaces to enable the implements
.
Without it, I had a couple of bugs when the fields didn't update. Took me some time to find and fix them. With this implementation, I was able to find a hidden name error and fix it right away. I'm satisfied with the solution.
Search Terms
interface, implements
Suggestion
Allow declaring that an interface "implements" another interface or interfaces, which means the compiler checks conformance, but unlike the "extends" clause, no members are inherited:
interface Foo { foo(): void; } interface Bar implements Foo { foo(): void; // must be present to satisfy type-checker bar(): void; }
Use Cases
It is very common for one interface to be an "extension" of another, but the "extends" keyword is not a universal way to make this fact explicit in code. Because of structural typing, the fact that one interface is assignable to another is true with or without "extends," so you might say the "extends" keyword serves primarily to inherit members, and secondarily to document and enforce the relationship between the two types. Inheriting members comes with a readability trade-off and is not always desirable, so it would be useful to be able to document and enforce the relationship between two interfaces without inheriting members.
Consider code such as:
import { GestureHandler } from './GestureHandler' import { DropTarget } from './DropTarget' export interface DragAndDropHandler extends GestureHandler { updateDropTarget(dropTarget: DropTaget): void; } function createDragAndDropHandler(/*...*/): DragAndDropHandler { //... }
While this code is not bad, it is notable that DragAndDropHandler omits some of its members simply because it has a relationship with GestureHandler. What are those members? What if I would like to declare them explicitly, just as I would if GestureHandler didn't exist, or if DragAndDropHandler were a class that implemented GestureHandler? I could write them in, but the compiler won't check that I have included all of them. I could omit
extends GestureHandler
, but then the type-checking will happen where DragAndDropHandler is used as a GestureHandler, not where it is defined.What I really want to do is be explicit about — and have the compiler check — that I am specifying all members of this interface, and also that it conforms to GestureHandler.
I would like to be able to write:
export interface DragAndDropHandler implements GestureHandler { updateDropTarget(dropTarget: DropTaget): void; move(gestureInfo: GestureInfo): void finish(gestureInfo: GestureInfo, success: boolean): void }
Examples
See above
Checklist
My suggestion meets these guidelines:
- [x] This wouldn't be a breaking change in existing TypeScript / JavaScript code
- [x] This wouldn't change the runtime behavior of existing JavaScript code
- [x] This could be implemented without emitting different JS based on the types of the expressions
- [x] This isn't a runtime feature (e.g. new expression-level syntax)
You can use class instead of interface to get the implements
feature
Another use case:
export interface Action {
//...
}
export interface Actions {
[key: string]: Action
}
//
export interface ExampleActions extends Actions {
testAction: {
// ...
}
testAction2: {
// ...
}
}
type ActionNames_Of_ExampleActions = keyof ExampleActions;
const exampleActionName: ActionNames_Of_ExampleActions = "gfhgfh"; // allowed, but we don't want it
Some could expect that ActionNames_Of_ExampleActions
is "testAction" | "testAction2"
but it isn't, it is string
because of super Actions
interface.
But with satisfies
keyword, that could be possible:
export interface Action {
//...
}
export interface Actions {
[key: string]: Action
}
//
export interface ExampleActions satisfies Actions {
testAction: {
// ...
}
testAction2: {
// ...
}
}
type ActionNames_Of_ExampleActions = keyof ExampleActions;
const exampleActionName: ActionNames_Of_ExampleActions = "gfhgfh"; //error, not allowed
Currently there is also another solution based on declared class types being same as interfaces
// point-interface.d.ts
export interface Point {
x: number
y: number
}
export class Point3D implements Point {
// ^! Class 'Point3D' incorrectly implements interface 'Point'.
z: number
}
// in case you need an actual interface to avoid `class extends imported_class` mistake
export interface Point4D extends class_Point4D {}
declare class class_Point4D implements Point {
// ^! Class 'class_Point4D' incorrectly implements interface 'Point'.
z: number
w: number
}
Search Terms
interface, implements
Suggestion
Allow declaring that an interface "implements" another interface or interfaces, which means the compiler checks conformance, but unlike the "extends" clause, no members are inherited:
Use Cases
It is very common for one interface to be an "extension" of another, but the "extends" keyword is not a universal way to make this fact explicit in code. Because of structural typing, the fact that one interface is assignable to another is true with or without "extends," so you might say the "extends" keyword serves primarily to inherit members, and secondarily to document and enforce the relationship between the two types. Inheriting members comes with a readability trade-off and is not always desirable, so it would be useful to be able to document and enforce the relationship between two interfaces without inheriting members.
Consider code such as:
While this code is not bad, it is notable that DragAndDropHandler omits some of its members simply because it has a relationship with GestureHandler. What are those members? What if I would like to declare them explicitly, just as I would if GestureHandler didn't exist, or if DragAndDropHandler were a class that implemented GestureHandler? I could write them in, but the compiler won't check that I have included all of them. I could omit
extends GestureHandler
, but then the type-checking will happen where DragAndDropHandler is used as a GestureHandler, not where it is defined.What I really want to do is be explicit about — and have the compiler check — that I am specifying all members of this interface, and also that it conforms to GestureHandler.
I would like to be able to write:
Examples
See above
Checklist
My suggestion meets these guidelines: