Open ajvincent opened 1 year ago
If I'm understanding your proposal correctly, this isn't something we could reasonably do, since it would break very reasonable code with no apparent workaround:
class Foo {
sample(s: string) { }
doSomething(func: Foo['sample']) { }
}
function myFunc(s: string) { }
// Illegal, property 'name' of myFunc is 'myFunc', not assignable to 'sample' in target type
(new Foo()).doSomething(myFunc);
@RyanCavanaugh, if I understood the initial suggestion right, I found myself limited by a pretty same issue with ClassFieldDecoratorContext.
interface ClassFieldDecoratorContext<
This = unknown,
Value = unknown,
> {
// ...
readonly name: string | symbol;
}
interface ClassFieldDecoratorContext<
This = unknown
Value = unknown
Name extends keyof This = keyof This
> {
// ...
readonly name: Name
}
I found myself in need of better type for 'name' field, while I was trying to implement the following code (it won't compile currently):
export class Model<JSON extends Record<string, any>> {
data: JSON
constructor(data: JSON) {
this.data = data
}
}
interface UserJSON {
created: string
}
class User extends Model<UserJSON> {
@Field((json) => new Date(json.created)) created!: Date // currently, doesn't work
}
export function Field<This extends Model<any>, Value, Name extends keyof This & keyof This['data']>(
reviver: This[Name] extends This['data'][Name] ? (json: This['data']) => This[Name] : undefined
) {
return function (
target: undefined,
context: ClassFieldDecoratorContext<This, Value, Name> // currently it has 2 generics, so error will be shown here
) {
return function (this: This) {
return reviver ? reviver(this.data) : this.data[context.name]
}
}
}
Let me know about your thoughts, it doesn't seem like a breaking change to me, but I surely can miss a lot of things. Thanks!
PS. Maybe I do not understand something in your example, but the parameter will be inferred to (s: string) => void anyway, so what does it have to do with fn name or especially DecoratorInterface?
lib Update Request
Configuration Check
My compilation target is
ESNext
and my lib isthe default
.Missing / Incorrect Definition
We could change the
name
field to:readonly name: Value extends { name: string } ? Value["name"] : (string | symbol);
. Most of the time, Value["name"] is juststring
, so this would be a non-breaking change (if slightly less intuitive).However, if I were to augment my method type definitions to have a name property, TypeScript could pick up on it:
Sample Code
Obligatory Playground link
Motivation
I've been experimenting with method decorators for about a week now, to implement some aspect-oriented programming (or at least, my understanding of it). One facet I haven't yet cracked is removing the need for the method name on a decorator:
Since the context has a name field of
string | symbol
type, I can't infer the name from that... I think.I am perfectly willing to be wrong and have this whole ticket closed as invalid!
Documentation Link
The ECMAScript 2023 specification for Function.name says I'm possibly wrong here: it specifies the
name
field must be a string (specifically, "not a symbol"). This could be just a specification bug with the introduction of symbol keys, but I am not ready to assert that at this time.