-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Experiment with always using parameters from base types for derived methods #23911
Comments
Couple of quick observations:
|
I agree - non-explicit syntactic switches are not something I enjoy. |
class Base {
method = (x: number) => {
// ...
}
}
class Derived extends Base {
method(x) {
// Does 'x' have the type 'number' here?
}
} Conveniently, this code is illegal. The converse example would just be a case for #10570 |
I really want to take this and #10570 as part of a "3.0 is a numerological excuse to make some good breaks" bundle. Regarding this example class Base {
method(x: "a" | "b") {
// ...
}
}
class Derived extends Base {
method(x = "a") {
// We have to make sure 'x' doesn't have type '"a"'
// which is both unsound and less useful.
}
} I think in any world, |
Interesting! Are there more breaks in this "bundle"? (Looking for example at #13971) |
Referencing a similar question regarding interfaces on SO and complication on implementing multiple interfaces: interface X { foo(i: string): string }
interface Y { foo(x: number): number }
class K implements X, Y {
foo(x: number): number
foo(x: string): string
foo(x: number | string) {
return x
}
} |
I'm definitely curious about whether there's been any more progress on this? At least from my perspective, it seems like inference of method parameters from the interface would be a pretty big ergonomics win (and happens relatively frequently, especially in library/framework code) |
Re:
Would a less strict take on it be more palatable? From what I can gather from the various issues/PRs towards this, the primary motivation seems to be improving the ergonomics, and is not so much about strictly checking classes to their parent types (we already have good assertions covering those cases). For example: infer the types of method arguments when E.g. interface Base { doThing(a: number): number }
class Foo implements Base {
doThing(a /* inferred to be number */) {
return a;
}
}
class Bar implements Base {
// already an error here:
// "Property 'doThing' in type 'Bar' is not assignable to the same property in base type 'Base'."
doThing(a: string /* not an error here */) {
return parseInt(a);
}
}
class Baz implements Base {
doThing(a: 1 | 2 | 3 /* not widened to number*/) {
return a;
}
} |
Any movement on this issue? |
This much like a lot of very similar suggestions only consider implicit type behaviour changing fairly subtlety. #36165 suggests a way to explicitly say "the same type as expected by base class" which solves the issue of derive types without rewriting the same parameter signature every time. |
Copying and following upstream definitions is complicated as this: https://github.com/falsandtru/spica/blob/master/src/promise.ts |
I don't see it that way. Per my long comment on #32082, type inheritance should use the nearest explicitly defined type from the ancestor classes, further narrowed by any interfaces implemented by the subclass. (See the linked comment for the full methodology and rationale.) If you follow that logic, then My proposal for this issue of parameter types of overridden methods in derived classes follows the logic of my comment on #32082. Such methods should be allowed to specify explicit parameter types that are compatible with the class's ancestor classes and interfaces, but in the absence of explicit types, then the types should be inherited from those ancestor classes and interfaces as I proposed, and only inferred from default values if the ancestor classes and interfaces have nothing to say about this method name.
This sentiment I agree with. 😄 So… how about TypeScript 4.0? |
The readme was asserting something that isn't in the TypeScript language. The issue for tracking support for this would be: microsoft/TypeScript#23911 (comment) See this playground for confirmation that `val` is inferred as `any` and not `boolean`: https://www.typescriptlang.org/play?ts=4.1.3#code/IYIwzgLgTsDGEAJYBthjAgYge2wHgBUA+BAbwCgEFRIZ4EB3KASwgFMA1YZAVzYAoAbtwBcCAgEoxg7MwAmAbnIBfcuRRoMAIWBQEbAB7sAdnIw58IXMjbBjJClSatO3PkO4SylKgh+rlIA
Any updates on this? |
Any updates on this? |
No Typescript expert but would love to see Typescript implement this somehow. There is also a gain in this when it comes to using Code example for more vibe: // utility.d.ts
abstract class Utility {
dirname(importMetaUrl: string): string;
resolvePath(importMetaUrl: string, relativePath: string): string;
// ...
}
declare const utility: Utility
export {utility, Utility} // utility.js
import { URL, fileURLToPath } from 'url'
import path from 'path'
class Utility {
/* Error: TS7006: Parameter 'importMetaUrl' implicitly has an 'any' type. */
dirname(importMetaUrl) {
const __filename = fileURLToPath(importMetaUrl)
return path.dirname(__filename)
}
/* Error: TS7006: Parameter 'importMetaUrl' implicitly has an 'any' type. */
/* Error: TS7006: Parameter 'relativePath' implicitly has an 'any' type. */
resolvePath(importMetaUrl, relativePath) {
return path.resolve(this.dirname(importMetaUrl), relativePath)
}
// ...
}
export const utility = new Utility() |
The JSDoc workaround is to use class Joiner {
/** @param {string[]} strings*/
join(...strings) { return strings.join('') }
}
class CommaJoiner extends Joiner {
/** @type {Joiner['join']} */
join(...strings) {
return strings.join(',');
}
}
class NewLineJoiner extends Joiner {
/** @type {Joiner['join']} */
join(...strings) {
return strings.join('\n');
}
} Though it would be nice to be able to use Not sure what kind of cast works on TS though. None of these do: interface IJoiner {
join: (...strings:string[]) => string;
};
class Joiner implements IJoiner {
join(...strings:string[]) { return strings.join('') }
}
class CommaJoiner extends Joiner implements IJoiner {
join(...strings){
return strings.join(',');
}
}
class NewLineJoiner extends Joiner {
join!: Joiner['join'];
join(...strings) {
return strings.join('\n');
}
} The third one is the closest, but TS says it's a duplicate identifier without being able to ignore. Maybe relaxing is the key? If TS could also take hints from |
This is already possible: class NewLineJoiner extends Joiner {
join: Joiner['join'] = (...strings) => {
return strings.join('\n')
}
} Despite using the arrow function, this supports |
look your memory app 😋 you lost prototype reference and class optimisation for a type feature, this is not suitable. |
then apply it to the arguments and return type: class NewLineJoiner extends Joiner {
join(...strings: Parameters<Joiner["join"]>): ReturnType<Joiner["join"]> {
return strings.join('\n');
}
} playground link. And if that feels way to verbose and repetative then possibly support #36165. |
This appears to be a longstanding TypeScript feature request that's not seeing much action: microsoft/TypeScript#23911 What I wanted was to automatically gain the type inference for the message type from the mapped type we're implementing, but what I got was a lack of reuse of the functions themselves. The extra redundancy is painful, but these really should be proper methods.
I think a first implementation could just cover the case when Whenever this pattern matches syntactically: class MySubClass extends MyBaseClass {
...
override myMethodName(arg1, ..., argN) {
...
}
...
} infer the types like this: class MySubClass extends MyBaseClass {
...
override myMethodName(arg1: Parameters<Joiner["join"]>[1], ..., argN: Parameters<Joiner["join"]>[N]): ReturnType<Joiner["join"]> {
...
}
...
} Of course this would have to be implemented in a more performant way. |
Note some related issues for doing this with properties rather than methods: #3667, #6118, #1373.
Goal
We want to make it easier for users to derive types without rewriting the same parameter signature every time
Potential ideas
noImplicitAny
(doesn't work for default initializers 😞)any
in all locations, opt in with another strictness flag (😞)Potential issues
Default initializers with more capable derived types
Default initializers that become less-capable via contextual types
Distinction between properties and methods
Would this work?
What about this?
Keywords: base type derived contextual contextually inherit methods implicit any
The text was updated successfully, but these errors were encountered: